gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] branch master updated (5d59b885 -> a03ce46d)


From: gnunet
Subject: [taler-merchant] branch master updated (5d59b885 -> a03ce46d)
Date: Tue, 02 May 2023 10:57:37 +0200

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

grothoff pushed a change to branch master
in repository merchant.

    from 5d59b885 -rate limiting
     new a7fe1d1b -newline
     new 583c01c6 first rough cut at merchant update for #7810 (still with 
known bugs)
     new 3db683d6 -fix FTBFS and /wire fetching logic
     new a03ce46d fix various memory-use issues

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/backend/taler-merchant-httpd_exchanges.c       | 829 ++++++++++++---------
 src/backend/taler-merchant-httpd_exchanges.h       |  34 +-
 src/backend/taler-merchant-httpd_helper.c          | 100 +++
 src/backend/taler-merchant-httpd_helper.h          |  17 +-
 .../taler-merchant-httpd_post-orders-ID-abort.c    |  26 +-
 .../taler-merchant-httpd_post-orders-ID-pay.c      |  57 +-
 .../taler-merchant-httpd_post-orders-ID-refund.c   |  26 +-
 .../taler-merchant-httpd_post-tips-ID-pickup.c     |  25 +-
 ...r-merchant-httpd_private-get-instances-ID-kyc.c |  21 +-
 .../taler-merchant-httpd_private-get-orders-ID.c   |  11 +-
 .../taler-merchant-httpd_private-get-reserves-ID.c |  11 +-
 ...-httpd_private-post-reserves-ID-authorize-tip.c |  10 +
 .../taler-merchant-httpd_private-post-reserves.c   |  95 ++-
 .../taler-merchant-httpd_private-post-transfers.c  |  19 +-
 src/backend/taler-merchant-httpd_reserves.c        |   7 +-
 src/backenddb/Makefile.am                          |   3 +
 src/backenddb/merchant-0005.sql                    |  30 +
 ...ch_progress.c => pg_delete_exchange_accounts.c} |  31 +-
 ...ert_account.h => pg_delete_exchange_accounts.h} |  21 +-
 ...tch_progress.c => pg_insert_exchange_account.c} |  45 +-
 ...tch_progress.h => pg_insert_exchange_account.h} |  31 +-
 ...accounts.c => pg_select_accounts_by_exchange.c} | 103 ++-
 ...accounts.h => pg_select_accounts_by_exchange.h} |  21 +-
 src/backenddb/plugin_merchantdb_postgres.c         |  45 +-
 src/backenddb/test_merchantdb.c                    |  22 +-
 src/include/taler_merchant_service.h               | 133 +++-
 src/include/taler_merchant_testing_lib.h           |  10 +-
 src/include/taler_merchantdb_plugin.h              |  91 ++-
 src/lib/merchant_api_get_reserve.c                 | 164 ++--
 src/lib/merchant_api_post_order_pay.c              |  29 +-
 src/lib/merchant_api_post_reserves.c               |  70 +-
 src/lib/merchant_api_tip_pickup.c                  |   2 +-
 src/merchant-tools/taler-merchant-setup-reserve.c  |  91 ++-
 src/testing/testing_api_cmd_checkserver.c          |  18 +-
 src/testing/testing_api_cmd_get_reserve.c          | 129 ++--
 src/testing/testing_api_cmd_post_reserves.c        |  21 +-
 src/testing/testing_api_cmd_post_transfers.c       |   4 +-
 src/testing/testing_api_cmd_post_using_templates.c |  80 +-
 src/testing/testing_api_cmd_testserver.c           |  43 +-
 39 files changed, 1514 insertions(+), 1011 deletions(-)
 copy src/backenddb/{pg_update_wirewatch_progress.c => 
pg_delete_exchange_accounts.c} (59%)
 copy src/backenddb/{pg_insert_account.h => pg_delete_exchange_accounts.h} (66%)
 copy src/backenddb/{pg_update_wirewatch_progress.c => 
pg_insert_exchange_account.c} (53%)
 copy src/backenddb/{pg_update_wirewatch_progress.h => 
pg_insert_exchange_account.h} (50%)
 copy src/backenddb/{pg_select_wirewatch_accounts.c => 
pg_select_accounts_by_exchange.c} (52%)
 copy src/backenddb/{pg_select_wirewatch_accounts.h => 
pg_select_accounts_by_exchange.h} (64%)

diff --git a/src/backend/taler-merchant-httpd_exchanges.c 
b/src/backend/taler-merchant-httpd_exchanges.c
index f0324c47..f4695184 100644
--- a/src/backend/taler-merchant-httpd_exchanges.c
+++ b/src/backend/taler-merchant-httpd_exchanges.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014-2021 Taler Systems SA
+  (C) 2014-2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free 
Software
@@ -24,11 +24,16 @@
 #include "taler-merchant-httpd_exchanges.h"
 #include "taler-merchant-httpd.h"
 
+/**
+ * How often do we retry DB transactions with soft errors?
+ */
+#define MAX_RETRIES 3
 
 /**
- * Delay after which we'll re-fetch key information from the exchange.
+ * Minimum delay after which we'll re-fetch key information from the exchange.
  */
-#define RELOAD_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 
2)
+#define MIN_RELOAD_DELAY GNUNET_TIME_relative_multiply ( \
+    GNUNET_TIME_UNIT_MINUTES, 2)
 
 /**
  * Delay after which we'll allow clients to force us to re-fetch key
@@ -94,11 +99,6 @@ struct TMH_EXCHANGES_FindOperation
    */
   struct Exchange *my_exchange;
 
-  /**
-   * Wire method we care about for fees, NULL if we do not care about wire 
fees.
-   */
-  char *wire_method;
-
   /**
    * Task scheduled to asynchronously return the result to
    * the find continuation.
@@ -129,11 +129,6 @@ struct FeesByWireMethod
    */
   char *wire_method;
 
-  /**
-   * Full payto URI of the exchange.
-   */
-  char *payto_uri;
-
   /**
    * Applicable fees, NULL if unknown/error.
    */
@@ -209,6 +204,11 @@ struct Exchange
    */
   struct GNUNET_TIME_Absolute first_retry;
 
+  /**
+   * How soon should we re-download /keys?
+   */
+  struct GNUNET_TIME_Timestamp keys_expiration;
+
   /**
    * How long should we wait between the next retry?
    */
@@ -225,11 +225,11 @@ struct Exchange
   struct GNUNET_SCHEDULER_Task *retry_task;
 
   /**
-   * true to indicate that there is an ongoing
-   * transfer we are waiting for,
-   * false to indicate that key data is up-to-date.
+   * Falsoe to indicate that there is a /keys request
+   * we are waiting for.
+   * True to indicate that /keys data is up-to-date.
    */
-  bool pending;
+  bool have_keys;
 
   /**
    * true if this exchange is from our configuration and
@@ -279,22 +279,17 @@ json_t *TMH_trusted_exchanges;
  * a particular exchange and what key the exchange is using.
  *
  * @param cls closure, will be `struct Exchange` so that
- *   when this function gets called, it will change the flag 'pending'
- *   to 'false'. Note: 'keys' is automatically saved inside the exchange's
+ *   when this function gets called, it will change the flag 'have_keys'
+ *   to 'true'. Note: 'keys' is automatically saved inside the exchange's
  *   handle, which is contained inside 'struct Exchange', when
- *   this callback is called. Thus, once 'pending' turns 'false',
+ *   this callback is called. Thus, once 'have_keys' turns 'true',
  *   it is safe to call 'TALER_EXCHANGE_get_keys()' on the exchange's handle,
  *   in order to get the "good" keys.
- * @param hr http response details
- * @param keys information about the various keys used
- *        by the exchange
- * @param compat version compatibility data
+ * @param kr response details
  */
 static void
 keys_mgmt_cb (void *cls,
-              const struct TALER_EXCHANGE_HttpResponse *hr,
-              const struct TALER_EXCHANGE_Keys *keys,
-              enum TALER_EXCHANGE_VersionCompatibility compat);
+              const struct TALER_EXCHANGE_KeysResponse *kr);
 
 
 /**
@@ -347,134 +342,189 @@ retry_exchange (void *cls)
  *
  * @param exchange connection to the exchange
  * @param master_pub public key of the exchange
- * @param wire_method name of the wire method (i.e. "iban")
- * @param payto_uri full payto URI of the exchange
- * @param fees fee structure for this method
+ * @param num_methods number of wire methods supported
+ * @param fbm wire fees by method
  * @return #TALER_EC_NONE on success
  */
 static enum TALER_ErrorCode
 process_wire_fees (struct Exchange *exchange,
                    const struct TALER_MasterPublicKeyP *master_pub,
-                   const char *wire_method,
-                   const char *payto_uri,
-                   const struct TALER_EXCHANGE_WireAggregateFees *fees)
+                   unsigned int num_methods,
+                   const struct TALER_EXCHANGE_WireFeesByMethod *fbm)
 {
-  struct FeesByWireMethod *f;
-  struct TALER_EXCHANGE_WireAggregateFees *endp;
-  struct TALER_EXCHANGE_WireAggregateFees *af;
-
-  for (f = exchange->wire_fees_head; NULL != f; f = f->next)
-    if (0 == strcasecmp (wire_method,
-                         f->wire_method))
-      break;
-  if (NULL == f)
+  for (unsigned int i = 0; i<num_methods; i++)
   {
-    f = GNUNET_new (struct FeesByWireMethod);
-    f->wire_method = GNUNET_strdup (wire_method);
-    f->payto_uri = GNUNET_strdup (payto_uri);
-    GNUNET_CONTAINER_DLL_insert (exchange->wire_fees_head,
-                                 exchange->wire_fees_tail,
-                                 f);
-  }
-  endp = f->af;
-  while ( (NULL != endp) &&
-          (NULL != endp->next) )
-    endp = endp->next;
-  while ( (NULL != endp) &&
-          (NULL != fees) &&
-          (GNUNET_TIME_timestamp_cmp (fees->start_date,
-                                      <,
-                                      endp->end_date)) )
-    fees = fees->next;
-  if ( (NULL != endp) &&
-       (NULL != fees) &&
-       (GNUNET_TIME_timestamp_cmp (fees->start_date,
-                                   !=,
-                                   endp->end_date)) )
-  {
-    /* Hole in the fee structure, not allowed! */
-    GNUNET_break_op (0);
-    return TALER_EC_MERCHANT_GENERIC_HOLE_IN_WIRE_FEE_STRUCTURE;
-  }
-  while (NULL != fees)
-  {
-    struct GNUNET_HashCode h_wire_method;
-    enum GNUNET_DB_QueryStatus qs;
+    const char *wire_method = fbm[i].method;
+    const struct TALER_EXCHANGE_WireAggregateFees *fees = fbm[i].fees_head;
+    struct FeesByWireMethod *f;
+    struct TALER_EXCHANGE_WireAggregateFees *endp;
+    struct TALER_EXCHANGE_WireAggregateFees *af;
 
-    af = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees);
-    *af = *fees;
-    GNUNET_CRYPTO_hash (wire_method,
-                        strlen (wire_method) + 1,
-                        &h_wire_method);
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Storing wire fee for `%s' and method `%s' at %s in DB; the 
fee is %s\n",
-                TALER_B2S (master_pub),
-                wire_method,
-                GNUNET_TIME_timestamp2s (af->start_date),
-                TALER_amount2s (&af->fees.wire));
-    TMH_db->preflight (TMH_db->cls);
-    if (GNUNET_OK !=
-        TMH_db->start (TMH_db->cls,
-                       "store wire fee"))
+    for (f = exchange->wire_fees_head; NULL != f; f = f->next)
+      if (0 == strcasecmp (wire_method,
+                           f->wire_method))
+        break;
+    if (NULL == f)
     {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Failed to start database transaction to store exchange wire 
fees (will try to continue anyway)!\n");
-      GNUNET_free (af);
-      fees = fees->next;
-      continue;
+      f = GNUNET_new (struct FeesByWireMethod);
+      f->wire_method = GNUNET_strdup (wire_method);
+      GNUNET_CONTAINER_DLL_insert (exchange->wire_fees_head,
+                                   exchange->wire_fees_tail,
+                                   f);
     }
-    qs = TMH_db->store_wire_fee_by_exchange (TMH_db->cls,
-                                             master_pub,
-                                             &h_wire_method,
-                                             &af->fees,
-                                             af->start_date,
-                                             af->end_date,
-                                             &af->master_sig);
-    if (0 > qs)
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Failed to persist exchange wire fees in merchant DB (will 
try to continue anyway)!\n");
-      GNUNET_free (af);
+    endp = f->af;
+    while ( (NULL != endp) &&
+            (NULL != endp->next) )
+      endp = endp->next;
+    while ( (NULL != endp) &&
+            (NULL != fees) &&
+            (GNUNET_TIME_timestamp_cmp (fees->start_date,
+                                        <,
+                                        endp->end_date)) )
       fees = fees->next;
-      TMH_db->rollback (TMH_db->cls);
-      continue;
-    }
-    if (0 == qs)
+    if ( (NULL != endp) &&
+         (NULL != fees) &&
+         (GNUNET_TIME_timestamp_cmp (fees->start_date,
+                                     !=,
+                                     endp->end_date)) )
     {
-      /* Entry was already in DB, fine, continue as if we had succeeded */
-      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                  "Fees already in DB, rolling back transaction attempt!\n");
-      TMH_db->rollback (TMH_db->cls);
+      /* Hole in the fee structure, not allowed! */
+      GNUNET_break_op (0);
+      return TALER_EC_MERCHANT_GENERIC_HOLE_IN_WIRE_FEE_STRUCTURE;
     }
-    if (0 < qs)
+    while (NULL != fees)
     {
-      /* Inserted into DB, make sure transaction completes */
-      qs = TMH_db->commit (TMH_db->cls);
+      struct GNUNET_HashCode h_wire_method;
+      enum GNUNET_DB_QueryStatus qs;
+
+      af = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees);
+      *af = *fees;
+      GNUNET_CRYPTO_hash (wire_method,
+                          strlen (wire_method) + 1,
+                          &h_wire_method);
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  "Storing wire fee for `%s' and method `%s' at %s in DB; the 
fee is %s\n",
+                  TALER_B2S (master_pub),
+                  wire_method,
+                  GNUNET_TIME_timestamp2s (af->start_date),
+                  TALER_amount2s (&af->fees.wire));
+      TMH_db->preflight (TMH_db->cls);
+      if (GNUNET_OK !=
+          TMH_db->start (TMH_db->cls,
+                         "store wire fee"))
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Failed to start database transaction to store exchange 
wire fees (will try to continue anyway)!\n");
+        GNUNET_free (af);
+        fees = fees->next;
+        continue;
+      }
+      qs = TMH_db->store_wire_fee_by_exchange (TMH_db->cls,
+                                               master_pub,
+                                               &h_wire_method,
+                                               &af->fees,
+                                               af->start_date,
+                                               af->end_date,
+                                               &af->master_sig);
       if (0 > qs)
       {
         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                     "Failed to persist exchange wire fees in merchant DB (will 
try to continue anyway)!\n");
         GNUNET_free (af);
         fees = fees->next;
+        TMH_db->rollback (TMH_db->cls);
         continue;
       }
-    }
-    af->next = NULL;
-    if (NULL == endp)
-      f->af = af;
-    else
-      endp->next = af;
-    endp = af;
-    fees = fees->next;
-  }
+      if (0 == qs)
+      {
+        /* Entry was already in DB, fine, continue as if we had succeeded */
+        GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                    "Fees already in DB, rolling back transaction attempt!\n");
+        TMH_db->rollback (TMH_db->cls);
+      }
+      if (0 < qs)
+      {
+        /* Inserted into DB, make sure transaction completes */
+        qs = TMH_db->commit (TMH_db->cls);
+        if (0 > qs)
+        {
+          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                      "Failed to persist exchange wire fees in merchant DB 
(will try to continue anyway)!\n");
+          GNUNET_free (af);
+          fees = fees->next;
+          continue;
+        }
+      }
+      af->next = NULL;
+      if (NULL == endp)
+        f->af = af;
+      else
+        endp->next = af;
+      endp = af;
+      fees = fees->next;
+    } /* all fees for this method */
+  } /* for all methods (i) */
   return TALER_EC_NONE;
 }
 
 
 /**
- * Function called with information about the wire accounts
- * of the exchange.  Stores the wire fees with the
- * exchange for laster use.
+ * Add account restriction @a a to array of @a restrictions.
+ *
+ * @param[in,out] restrictions JSON array to build
+ * @param r restriction to add to @a restrictions
+ * @return #GNUNET_SYSERR if @a r is malformed
+ */
+static enum GNUNET_GenericReturnValue
+add_restriction (json_t *restrictions,
+                 const struct TALER_EXCHANGE_AccountRestriction *r)
+{
+  json_t *jr;
+
+  jr = NULL;
+  switch (r->type)
+  {
+  case TALER_EXCHANGE_AR_INVALID:
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  case TALER_EXCHANGE_AR_DENY:
+    jr = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("type",
+                               "deny")
+      );
+    break;
+  case TALER_EXCHANGE_AR_REGEX:
+    jr = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string (
+        "type",
+        "regex"),
+      GNUNET_JSON_pack_string (
+        "regex",
+        r->details.regex.posix_egrep),
+      GNUNET_JSON_pack_string (
+        "human_hint",
+        r->details.regex.human_hint),
+      GNUNET_JSON_pack_object_incref (
+        "human_hint_i18n",
+        (json_t *) r->details.regex.human_hint_i18n)
+      );
+    break;
+  }
+  if (NULL == jr)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  GNUNET_assert (0 ==
+                 json_array_append_new (restrictions,
+                                        jr));
+  return GNUNET_OK;
+
+}
+
+
+/**
+ * Function called with information about the wire accounts of the exchange.
  *
  * @param exchange the exchange
  * @param master_pub public key of the exchange
@@ -488,28 +538,95 @@ process_wire_accounts (struct Exchange *exchange,
                        unsigned int accounts_len,
                        const struct TALER_EXCHANGE_WireAccount *accounts)
 {
-  for (unsigned int i = 0; i<accounts_len; i++)
+  for (unsigned int r = 0; r<MAX_RETRIES; r++)
   {
-    enum TALER_ErrorCode ec;
-    char *method;
+    enum GNUNET_DB_QueryStatus qs;
 
-    method = TALER_payto_get_method (accounts[i].payto_uri);
-    if (NULL == method)
+    if (GNUNET_OK !=
+        TMH_db->start (TMH_db->cls,
+                       "update_exchange_accounts"))
     {
-      /* malformed payto:// URI returned by exchange */
-      GNUNET_break_op (0);
-      return TALER_EC_GENERIC_PAYTO_URI_MALFORMED;
+      TMH_db->rollback (TMH_db->cls);
+      GNUNET_break (0);
+      return TALER_EC_GENERIC_DB_START_FAILED;
     }
-    ec = process_wire_fees (exchange,
-                            master_pub,
-                            method,
-                            accounts[i].payto_uri,
-                            accounts[i].fees);
-    GNUNET_free (method);
-    if (TALER_EC_NONE != ec)
-      return ec;
+    qs = TMH_db->delete_exchange_accounts (TMH_db->cls,
+                                           master_pub);
+    if (qs < 0)
+    {
+      TMH_db->rollback (TMH_db->cls);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        continue;
+      GNUNET_break (0);
+      return TALER_EC_GENERIC_DB_STORE_FAILED;
+    }
+    for (unsigned int i = 0; i<accounts_len; i++)
+    {
+      const struct TALER_EXCHANGE_WireAccount *account = &accounts[i];
+      json_t *debit_restrictions;
+      json_t *credit_restrictions;
+
+      debit_restrictions = json_array ();
+      GNUNET_assert (NULL != debit_restrictions);
+      credit_restrictions = json_array ();
+      GNUNET_assert (NULL != credit_restrictions);
+      for (unsigned int j = 0; j<account->debit_restrictions_length; j++)
+      {
+        if (GNUNET_OK !=
+            add_restriction (debit_restrictions,
+                             &account->debit_restrictions[j]))
+        {
+          TMH_db->rollback (TMH_db->cls);
+          GNUNET_break (0);
+          json_decref (debit_restrictions);
+          json_decref (credit_restrictions);
+          return TALER_EC_MERCHANT_GENERIC_EXCHANGE_WIRE_REQUEST_FAILED;
+        }
+      }
+      for (unsigned int j = 0; j<account->credit_restrictions_length; j++)
+      {
+        if (GNUNET_OK !=
+            add_restriction (credit_restrictions,
+                             &account->credit_restrictions[j]))
+        {
+          TMH_db->rollback (TMH_db->cls);
+          GNUNET_break (0);
+          json_decref (debit_restrictions);
+          json_decref (credit_restrictions);
+          return TALER_EC_MERCHANT_GENERIC_EXCHANGE_WIRE_REQUEST_FAILED;
+        }
+      }
+      qs = TMH_db->insert_exchange_account (TMH_db->cls,
+                                            master_pub,
+                                            account->payto_uri,
+                                            account->conversion_url,
+                                            debit_restrictions,
+                                            credit_restrictions,
+                                            &account->master_sig);
+      json_decref (debit_restrictions);
+      json_decref (credit_restrictions);
+      if (qs < 0)
+      {
+        TMH_db->rollback (TMH_db->cls);
+        if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+          goto outer;
+        GNUNET_break (0);
+        return TALER_EC_GENERIC_DB_STORE_FAILED;
+      }
+    }
+    qs = TMH_db->commit (TMH_db->cls);
+    if (qs < 0)
+    {
+      TMH_db->rollback (TMH_db->cls);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+        continue;
+      GNUNET_break (0);
+      return TALER_EC_GENERIC_DB_COMMIT_FAILED;
+    }
+    return TALER_EC_NONE;
+outer:;
   }
-  return TALER_EC_NONE;
+  return TALER_EC_GENERIC_DB_SOFT_FAILURE;
 }
 
 
@@ -567,89 +684,72 @@ process_find_operations (struct Exchange *exchange)
 {
   struct TMH_EXCHANGES_FindOperation *fn;
   struct GNUNET_TIME_Timestamp now;
-  bool need_wire;
 
+  if (! exchange->have_wire)
+    return true;
   now = GNUNET_TIME_timestamp_get ();
-  need_wire = false;
-  for (struct TMH_EXCHANGES_FindOperation *fo = exchange->fo_head;
-       NULL != fo;
-       fo = fn)
+  for (struct FeesByWireMethod *fbw = exchange->wire_fees_head;
+       NULL != fbw;
+       fbw = fbw->next)
   {
-    const struct FeesByWireMethod *fbw;
+    bool removed = false;
 
-    fn = fo->next;
-    if (NULL != fo->wire_method)
+    while ( (NULL != fbw->af) &&
+            (GNUNET_TIME_timestamp_cmp (fbw->af->end_date,
+                                        <,
+                                        now)) )
     {
-      /* Find fee structure for our wire method */
-      fbw = get_wire_fees (exchange,
-                           now,
-                           fo->wire_method);
-      if (NULL == fbw)
-      {
-        need_wire = true;
-        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                    "Missing wire fees for exchange %s and method %s\n",
-                    exchange->url,
-                    fo->wire_method);
-        /* Do not warn if this is before our first attempt */
-        if (! GNUNET_TIME_relative_is_zero (exchange->wire_retry_delay))
-          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                      "Exchange does not support `%s' wire method (will retry 
later)\n",
-                      fo->wire_method);
-        fbw = NULL;
-        continue;
-      }
-      if (NULL == fbw->af)
-      {
-        /* Disagreement on the current time */
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "Exchange has no wire fees configured for `%s' wire method 
(will retry later)\n",
-                    fo->wire_method);
-        fbw = NULL;
-        continue;
-      }
-      if (GNUNET_TIME_timestamp_cmp (fbw->af->start_date,
-                                     >,
-                                     now))
+      struct TALER_EXCHANGE_WireAggregateFees *af = fbw->af;
+
+      fbw->af = af->next;
+      GNUNET_free (af);
+      removed = true;
+    }
+    if (NULL == fbw->af)
+    {
+      /* Disagreement on the current time */
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Exchange has no wire fees configured for `%s' wire 
method\n",
+                  fbw->wire_method);
+      if (removed)
       {
-        /* Disagreement on the current time */
-        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                    "Exchange's earliest fee is %s ahead of our time. Clock 
skew issue?\n",
-                    GNUNET_TIME_relative2s (
-                      GNUNET_TIME_absolute_get_remaining (
-                        fbw->af->start_date.abs_time),
-                      true));
-        fbw = NULL;
-        continue;
+        exchange->have_wire = false;
+        return true; /* We just removed previous fees, try fetching update */
       }
     }
-    else
+    if (GNUNET_TIME_timestamp_cmp (fbw->af->start_date,
+                                   >,
+                                   now))
     {
-      /* no wire transfer method given, so we yield no fee */
-      fbw = NULL;
+      /* Disagreement on the current time */
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Exchange's earliest fee is %s ahead of our time. Clock skew 
issue?\n",
+                  GNUNET_TIME_relative2s (
+                    GNUNET_TIME_absolute_get_remaining (
+                      fbw->af->start_date.abs_time),
+                    true));
     }
-    {
-      struct TALER_EXCHANGE_HttpResponse hr = {
-        .http_status = MHD_HTTP_OK,
-      };
+  }
 
-      if ( (NULL != fo->wire_method) &&
-           (! exchange->have_wire) )
-      {
-        /* We needed /wire, but didn't get it. That's not "200 OK". */
-        hr.http_status = MHD_HTTP_BAD_GATEWAY;
-        hr.ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_WIRE_REQUEST_FAILED;
-      }
-      fo->fc (fo->fc_cls,
-              &hr,
-              exchange->conn,
-              (NULL != fbw) ? fbw->payto_uri : NULL,
-              (NULL != fbw) ? &fbw->af->fees.wire : NULL,
-              exchange->trusted);
-    }
+  if (! exchange->have_wire)
+    return true; /* need /wire response to continue */
+  fn = NULL;
+  for (struct TMH_EXCHANGES_FindOperation *fo = exchange->fo_head;
+       NULL != fo;
+       fo = fn)
+  {
+    struct TALER_EXCHANGE_HttpResponse hr = {
+      .http_status = MHD_HTTP_OK,
+    };
+
+    fo->fc (fo->fc_cls,
+            &hr,
+            exchange->conn,
+            exchange->trusted);
+    fn = fo->next;
     TMH_EXCHANGES_find_exchange_cancel (fo);
   }
-  return need_wire;
+  return false;
 }
 
 
@@ -664,19 +764,15 @@ wire_task_cb (void *cls);
  * If the request fails to generate a valid response from the
  * exchange, @a http_status will also be zero.
  *
- * Must only be called if 'exchange->pending' is #GNUNET_NO,
+ * Must only be called if 'exchange->have_keys' is true.
  * that is #TALER_EXCHANGE_get_keys() will succeed.
  *
  * @param cls closure, a `struct Exchange`
- * @param hr HTTP response details
- * @param accounts_len length of the @a accounts array
- * @param accounts list of wire accounts of the exchange, NULL on error
+ * @param wr response details
  */
 static void
 handle_wire_data (void *cls,
-                  const struct TALER_EXCHANGE_HttpResponse *hr,
-                  unsigned int accounts_len,
-                  const struct TALER_EXCHANGE_WireAccount *accounts)
+                  const struct TALER_EXCHANGE_WireResponse *wr)
 {
   struct Exchange *exchange = cls;
   const struct TALER_EXCHANGE_Keys *keys;
@@ -685,7 +781,7 @@ handle_wire_data (void *cls,
   exchange->wire_request = NULL;
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Received /wire response\n");
-  if (MHD_HTTP_OK != hr->http_status)
+  if (MHD_HTTP_OK != wr->hr.http_status)
   {
     struct TMH_EXCHANGES_FindOperation *fo;
 
@@ -693,16 +789,14 @@ handle_wire_data (void *cls,
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Failed to obtain /wire details from `%s': %u/%d\n",
                 exchange->url,
-                hr->http_status,
-                hr->ec);
+                wr->hr.http_status,
+                wr->hr.ec);
     while (NULL != (fo = exchange->fo_head))
     {
       fo->fc (fo->fc_cls,
-              hr,
+              &wr->hr,
               exchange->conn,
-              NULL,
-              NULL,
-              GNUNET_NO);
+              false);
       TMH_EXCHANGES_find_exchange_cancel (fo);
     }
     return;
@@ -711,8 +805,13 @@ handle_wire_data (void *cls,
   GNUNET_assert (NULL != keys);
   ecx = process_wire_accounts (exchange,
                                &keys->master_pub,
-                               accounts_len,
-                               accounts);
+                               wr->details.ok.accounts_len,
+                               wr->details.ok.accounts);
+  if (TALER_EC_NONE == ecx)
+    ecx = process_wire_fees (exchange,
+                             &keys->master_pub,
+                             wr->details.ok.fees_len,
+                             wr->details.ok.fees);
   if (TALER_EC_NONE != ecx)
   {
     /* Report hard failure to all callbacks! */
@@ -720,7 +819,7 @@ handle_wire_data (void *cls,
     struct TALER_EXCHANGE_HttpResponse hrx = {
       .ec = ecx,
       .http_status = 0,
-      .reply = hr->reply
+      .reply = wr->hr.reply
     };
 
     GNUNET_break_op (0);
@@ -730,9 +829,7 @@ handle_wire_data (void *cls,
       fo->fc (fo->fc_cls,
               &hrx,
               NULL,
-              NULL,
-              NULL,
-              GNUNET_NO);
+              false);
       TMH_EXCHANGES_find_exchange_cancel (fo);
     }
     return;
@@ -750,11 +847,11 @@ handle_wire_data (void *cls,
                                   exchange->wire_retry_delay);
     exchange->wire_retry_delay = RETRY_BACKOFF (exchange->wire_retry_delay);
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Exchange does not support our wire method. Retrying in %s\n",
-
+                "Need %s/wire, next download attempt in %s\n",
+                exchange->url,
                 GNUNET_STRINGS_relative_time_to_string (
                   exchange->wire_retry_delay,
-                  GNUNET_YES));
+                  true));
     exchange->wire_task
       = GNUNET_SCHEDULER_add_delayed (exchange->wire_retry_delay,
                                       &wire_task_cb,
@@ -769,7 +866,7 @@ handle_wire_data (void *cls,
  * the callback.  If requests without /wire data remain,
  * retry the /wire request after some delay.
  *
- * Must only be called if 'exchange->pending' is #GNUNET_NO,
+ * Must only be called if 'exchange->have_keys' is true,
  * that is #TALER_EXCHANGE_get_keys() will succeed.
  *
  * @param cls a `struct Exchange` to check
@@ -780,7 +877,7 @@ wire_task_cb (void *cls)
   struct Exchange *exchange = cls;
 
   exchange->wire_task = NULL;
-  GNUNET_assert (! exchange->pending);
+  GNUNET_assert (exchange->have_keys);
   if (! process_find_operations (exchange))
     return; /* no more need */
   GNUNET_assert (NULL == exchange->wire_request);
@@ -818,7 +915,6 @@ free_exchange_entry (struct Exchange *exchange)
       GNUNET_free (af);
     }
     GNUNET_free (f->wire_method);
-    GNUNET_free (f->payto_uri);
     GNUNET_free (f);
   }
   if (NULL != exchange->wire_request)
@@ -854,16 +950,14 @@ free_exchange_entry (struct Exchange *exchange)
  *
  * @param exchange exchange that failed
  * @param hr details about the HTTP reply
- * @param compat version compatibility data
  */
 static void
 fail_and_retry (struct Exchange *exchange,
-                const struct TALER_EXCHANGE_HttpResponse *hr,
-                enum TALER_EXCHANGE_VersionCompatibility compat)
+                const struct TALER_EXCHANGE_HttpResponse *hr)
 {
   struct TMH_EXCHANGES_FindOperation *fo;
 
-  exchange->pending = true;
+  exchange->have_keys = false;
   if (NULL != exchange->wire_request)
   {
     TALER_EXCHANGE_wire_cancel (exchange->wire_request);
@@ -879,23 +973,9 @@ fail_and_retry (struct Exchange *exchange,
     fo->fc (fo->fc_cls,
             hr,
             NULL,
-            NULL,
-            NULL,
-            GNUNET_NO);
+            false);
     TMH_EXCHANGES_find_exchange_cancel (fo);
   }
-  if (TALER_EXCHANGE_VC_INCOMPATIBLE_NEWER == compat)
-  {
-    /* Log hard error: we likely need admin help! */
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "Exchange `%s' runs an incompatible more recent version of the 
Taler protocol. Will not retry. This client may need to be updated.\n",
-                exchange->url);
-    /* Theoretically, the exchange could downgrade,
-       but let's not be too aggressive about retries
-       on this one. */
-    exchange->retry_delay = GNUNET_TIME_relative_max (GNUNET_TIME_UNIT_HOURS,
-                                                      exchange->retry_delay);
-  }
   if ( (NULL == exchange->fo_head) &&
        (TALER_EC_GENERIC_CONFIGURATION_INVALID == hr->ec) )
   {
@@ -910,7 +990,7 @@ fail_and_retry (struct Exchange *exchange,
               (int) hr->ec,
               hr->http_status,
               GNUNET_STRINGS_relative_time_to_string (exchange->retry_delay,
-                                                      GNUNET_YES));
+                                                      true));
   if (NULL != exchange->retry_task)
     GNUNET_SCHEDULER_cancel (exchange->retry_task);
   exchange->first_retry = GNUNET_TIME_relative_to_absolute (
@@ -921,40 +1001,21 @@ fail_and_retry (struct Exchange *exchange,
 }
 
 
-/**
- * Function called with information about who is auditing
- * a particular exchange and what key the exchange is using.
- *
- * @param cls closure, will be `struct Exchange` so that
- *   when this function gets called, it will change the flag 'pending'
- *   to 'false'. Note: 'keys' is automatically saved inside the exchange's
- *   handle, which is contained inside 'struct Exchange', when
- *   this callback is called. Thus, once 'pending' turns 'false',
- *   it is safe to call 'TALER_EXCHANGE_get_keys()' on the exchange's handle,
- *   in order to get the "good" keys.
- * @param hr http response details
- * @param keys information about the various keys used
- *        by the exchange
- * @param compat version compatibility data
- */
 static void
 keys_mgmt_cb (void *cls,
-              const struct TALER_EXCHANGE_HttpResponse *hr,
-              const struct TALER_EXCHANGE_Keys *keys,
-              enum TALER_EXCHANGE_VersionCompatibility compat)
+              const struct TALER_EXCHANGE_KeysResponse *kr)
 {
   struct Exchange *exchange = cls;
-  struct GNUNET_TIME_Timestamp expire;
   struct GNUNET_TIME_Relative delay;
+  const struct TALER_EXCHANGE_Keys *keys;
 
-  if ( (MHD_HTTP_OK != hr->http_status) ||
-       (NULL == keys) )
+  if (MHD_HTTP_OK != kr->hr.http_status)
   {
     fail_and_retry (exchange,
-                    hr,
-                    compat);
+                    &kr->hr);
     return;
   }
+  keys = kr->details.ok.keys;
   if ( (exchange->trusted) &&
        (0 != GNUNET_memcmp (&exchange->master_pub,
                             &keys->master_pub)) )
@@ -982,19 +1043,6 @@ keys_mgmt_cb (void *cls,
         exchange->trusted = true; /* same exchange, different URL => trust 
applies */
     }
   }
-  if (0 != (TALER_EXCHANGE_VC_NEWER & compat))
-  {
-    /* Warn user exactly once about need to upgrade */
-    static int once;
-
-    if (0 == once)
-    {
-      once = 1;
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Exchange `%s' runs a more recent version of the Taler 
protocol. You may want to update this client.\n",
-                  exchange->url);
-    }
-  }
 
   /* store exchange online signing keys in our DB */
   for (unsigned int i = 0; i<keys->num_sign_keys; i++)
@@ -1015,32 +1063,24 @@ keys_mgmt_cb (void *cls,
     {
       GNUNET_break (0);
       fail_and_retry (exchange,
-                      hr,
-                      compat);
+                      &kr->hr);
       return;
     }
   }
 
-  exchange->first_retry = GNUNET_TIME_relative_to_absolute (RELOAD_DELAY);
-  expire = TALER_EXCHANGE_check_keys_current (exchange->conn,
-                                              TALER_EXCHANGE_CKF_NONE);
-  if (0 == GNUNET_TIME_absolute_is_zero (expire.abs_time))
-  {
-    delay = RELOAD_DELAY;
-  }
-  else
-  {
-    delay = GNUNET_TIME_absolute_get_remaining (expire.abs_time);
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "/keys response from expires at %s! Retrying at that time!\n",
-                GNUNET_TIME_absolute2s (expire.abs_time));
-  }
-  if (GNUNET_TIME_relative_is_zero (delay))
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                "/keys response from exchange expired immediately! Retrying in 
1 minute.\n");
-    delay = GNUNET_TIME_UNIT_MINUTES;
-  }
+  exchange->first_retry
+    = GNUNET_TIME_relative_to_absolute (MIN_RELOAD_DELAY);
+  exchange->keys_expiration
+    = TALER_EXCHANGE_check_keys_current (exchange->conn,
+                                         TALER_EXCHANGE_CKF_NONE);
+  delay = GNUNET_TIME_absolute_get_remaining (
+    exchange->keys_expiration.abs_time);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "/keys response from expires at %s! Retrying at that time!\n",
+              GNUNET_TIME_absolute2s (
+                exchange->keys_expiration.abs_time));
+  delay = GNUNET_TIME_relative_max (delay,
+                                    MIN_RELOAD_DELAY);
   exchange->retry_delay = GNUNET_TIME_UNIT_ZERO;
   if (NULL != exchange->retry_task)
     GNUNET_SCHEDULER_cancel (exchange->retry_task);
@@ -1048,16 +1088,17 @@ keys_mgmt_cb (void *cls,
     = GNUNET_SCHEDULER_add_delayed (delay,
                                     &retry_exchange,
                                     exchange);
-  exchange->pending = false;
+  exchange->have_keys = true;
   if ( (process_find_operations (exchange)) &&
        (NULL == exchange->wire_request) &&
        (NULL == exchange->wire_task) )
   {
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Got key data, but also need wire data. Will request /wire 
now\n");
-    exchange->wire_request = TALER_EXCHANGE_wire (exchange->conn,
-                                                  &handle_wire_data,
-                                                  exchange);
+    exchange->wire_request
+      = TALER_EXCHANGE_wire (exchange->conn,
+                             &handle_wire_data,
+                             exchange);
   }
 }
 
@@ -1076,29 +1117,49 @@ return_result (void *cls)
   fo->at = NULL;
   if ( (process_find_operations (exchange)) &&
        (NULL == exchange->wire_request) &&
-       (! exchange->pending) &&
+       (exchange->have_keys) &&
        (NULL != exchange->wire_task) )
   {
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Do not have current wire data. Will re-request /wire in 1 
minute\n");
+                "Do not have current wire data. Will re-request /wire in %s\n",
+                GNUNET_STRINGS_relative_time_to_string (
+                  exchange->wire_retry_delay,
+                  true));
     exchange->wire_task
-      = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES,
+      = GNUNET_SCHEDULER_add_delayed (exchange->wire_retry_delay,
                                       &wire_task_cb,
                                       exchange);
   }
 }
 
 
+/**
+ * Lookup exchange by @a exchange_url.
+ *
+ * @param exchange_url base URL to match against
+ * @return NULL if exchange is not yet known
+ */
+static struct Exchange *
+lookup_exchange (const char *exchange_url)
+{
+  for (struct Exchange *exchange = exchange_head;
+       NULL != exchange;
+       exchange = exchange->next)
+    if (0 == strcmp (exchange->url,
+                     exchange_url))
+      return exchange;
+  return NULL;
+}
+
+
 struct TMH_EXCHANGES_FindOperation *
 TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
-                             const char *wire_method,
-                             int force_reload,
+                             bool force_reload,
                              TMH_EXCHANGES_FindContinuation fc,
                              void *fc_cls)
 {
   struct Exchange *exchange;
   struct TMH_EXCHANGES_FindOperation *fo;
-  struct GNUNET_TIME_Timestamp now;
 
   if (NULL == merchant_curl_ctx)
   {
@@ -1109,28 +1170,12 @@ TMH_EXCHANGES_find_exchange (const char 
*chosen_exchange,
               "Trying to find chosen exchange `%s'\n",
               chosen_exchange);
   /* Check if the exchange is known */
-  for (exchange = exchange_head; NULL != exchange; exchange = exchange->next)
-  {
-    /* test it by checking URL */
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Comparing chosen exchange url '%s' with known url '%s'.\n",
-                chosen_exchange,
-                exchange->url);
-    if (0 == strcmp (exchange->url,
-                     chosen_exchange))
-    {
-      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                  "The exchange `%s' is already known (good)\n",
-                  chosen_exchange);
-      break;
-    }
-  }
+  exchange = lookup_exchange (chosen_exchange);
   if (NULL == exchange)
   {
     /* This is a new exchange */
     exchange = GNUNET_new (struct Exchange);
     exchange->url = GNUNET_strdup (chosen_exchange);
-    exchange->pending = true;
     GNUNET_CONTAINER_DLL_insert (exchange_head,
                                  exchange_tail,
                                  exchange);
@@ -1143,13 +1188,14 @@ TMH_EXCHANGES_find_exchange (const char 
*chosen_exchange,
   fo->fc = fc;
   fo->fc_cls = fc_cls;
   fo->my_exchange = exchange;
-  if (NULL != wire_method)
-    fo->wire_method = GNUNET_strdup (wire_method);
   GNUNET_CONTAINER_DLL_insert (exchange->fo_head,
                                exchange->fo_tail,
                                fo);
-  if ( (force_reload) &&
-       (GNUNET_TIME_absolute_is_past (exchange->first_retry)) )
+
+  if ( (GNUNET_TIME_absolute_is_past (exchange->first_retry)) &&
+       (force_reload ||
+        (GNUNET_TIME_absolute_is_past (
+           exchange->keys_expiration.abs_time))) )
   {
     /* increment exponential-backoff */
     exchange->retry_delay = RETRY_BACKOFF (exchange->retry_delay);
@@ -1162,25 +1208,31 @@ TMH_EXCHANGES_find_exchange (const char 
*chosen_exchange,
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "/keys retry forced, waiting until %s\n",
                 GNUNET_TIME_absolute2s (exchange->first_retry));
-    /* NOTE: return value tells us how long /keys should still
-       be valid. */
-    (void) TALER_EXCHANGE_check_keys_current (exchange->conn,
-                                              
TALER_EXCHANGE_CKF_FORCE_DOWNLOAD);
+    if (NULL == exchange->conn)
+    {
+      /* Not connected at all yet */
+      exchange->retry_task
+        = GNUNET_SCHEDULER_add_now (&retry_exchange,
+                                    exchange);
+    }
+    else
+    {
+      /* Use existing connection, but update /keys */
+      exchange->keys_expiration
+        = TALER_EXCHANGE_check_keys_current (exchange->conn,
+                                             
TALER_EXCHANGE_CKF_FORCE_DOWNLOAD);
+    }
     return fo;
   }
 
-  now = GNUNET_TIME_timestamp_get ();
-  if ( (! exchange->pending) &&
-       ( (NULL == fo->wire_method) ||
-         (NULL != get_wire_fees (exchange,
-                                 now,
-                                 fo->wire_method)) ) )
+  if ( (exchange->have_keys) &&
+       (exchange->have_wire) )
   {
     /* We are not currently waiting for a reply, immediately
        return result */
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "The exchange `%s' is ready\n",
-                chosen_exchange);
+                exchange->url);
     GNUNET_assert (NULL == fo->at);
     fo->at = GNUNET_SCHEDULER_add_now (&return_result,
                                        fo);
@@ -1190,23 +1242,28 @@ TMH_EXCHANGES_find_exchange (const char 
*chosen_exchange,
   /* If new or resumed, (re)try fetching /keys */
   if ( (NULL == exchange->conn) &&
        (NULL == exchange->retry_task) &&
-       (exchange->pending) )
+       (! exchange->have_keys) )
   {
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Do not have current /keys data for `%s'. Will request /keys 
now\n",
-                chosen_exchange);
+                exchange->url);
     exchange->retry_task = GNUNET_SCHEDULER_add_now (&retry_exchange,
                                                      exchange);
+    return fo;
   }
-  else if ( (! exchange->pending) &&
-            (NULL == exchange->wire_task) &&
-            (NULL == exchange->wire_request) )
+  if ( (exchange->have_keys) &&
+       (! exchange->have_wire) &&
+       (NULL == exchange->wire_task) &&
+       (NULL == exchange->wire_request) )
   {
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Do not have required wire data. Will re-request /wire now\n");
+                "Do not have required /wire data. Will re-request %s/wire 
now\n",
+                exchange->url);
     exchange->wire_task = GNUNET_SCHEDULER_add_now (&wire_task_cb,
                                                     exchange);
+    return fo;
   }
+  /* No activity to launch, we are already doing so */
   return fo;
 }
 
@@ -1224,7 +1281,6 @@ TMH_EXCHANGES_find_exchange_cancel (struct 
TMH_EXCHANGES_FindOperation *fo)
   GNUNET_CONTAINER_DLL_remove (exchange->fo_head,
                                exchange->fo_tail,
                                fo);
-  GNUNET_free (fo->wire_method);
   GNUNET_free (fo);
 }
 
@@ -1318,13 +1374,46 @@ accept_exchanges (void *cls,
   GNUNET_CONTAINER_DLL_insert (exchange_head,
                                exchange_tail,
                                exchange);
-  exchange->pending = true;
   GNUNET_assert (NULL == exchange->retry_task);
   exchange->retry_task = GNUNET_SCHEDULER_add_now (&retry_exchange,
                                                    exchange);
 }
 
 
+enum GNUNET_GenericReturnValue
+TMH_EXCHANGES_lookup_wire_fee (const char *exchange_url,
+                               const char *wire_method,
+                               struct TALER_Amount *wire_fee)
+{
+  struct Exchange *exchange;
+  const struct FeesByWireMethod *fbm;
+  const struct TALER_EXCHANGE_WireAggregateFees *af;
+
+  exchange = lookup_exchange (exchange_url);
+  if (NULL == exchange)
+  {
+    fprintf (stderr,
+             "No %s yet\n",
+             exchange_url);
+    return GNUNET_SYSERR;
+  }
+  if (! exchange->have_wire)
+  {
+    fprintf (stderr,
+             "No wire yet\n");
+    return GNUNET_SYSERR;
+  }
+  fbm = get_wire_fees (exchange,
+                       GNUNET_TIME_timestamp_get (),
+                       wire_method);
+  if (NULL == fbm)
+    return GNUNET_NO;
+  af = fbm->af;
+  *wire_fee = af->fees.wire;
+  return GNUNET_OK;
+}
+
+
 enum GNUNET_GenericReturnValue
 TMH_EXCHANGES_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
 {
diff --git a/src/backend/taler-merchant-httpd_exchanges.h 
b/src/backend/taler-merchant-httpd_exchanges.h
index df68b9a5..2f4e69fe 100644
--- a/src/backend/taler-merchant-httpd_exchanges.h
+++ b/src/backend/taler-merchant-httpd_exchanges.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014-2020 Taler Systems SA
+  (C) 2014-2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -61,16 +61,12 @@ TMH_EXCHANGES_done (void);
  * @param cls closure
  * @param hr HTTP response details
  * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
  * @param exchange_trusted true if this exchange is trusted by config
  */
 typedef void
 (*TMH_EXCHANGES_FindContinuation)(void *cls,
                                   const struct TALER_EXCHANGE_HttpResponse *hr,
                                   struct TALER_EXCHANGE_Handle *eh,
-                                  const char *payto_uri,
-                                  const struct TALER_Amount *wire_fee,
                                   bool exchange_trusted);
 
 
@@ -86,23 +82,35 @@ struct TMH_EXCHANGES_FindOperation;
  * NULL for the exchange.
  *
  * @param chosen_exchange URL of the exchange we would like to talk to
- * @param wire_method the wire method we will use with @a chosen_exchange, 
NULL for none
- * @param force_reload try to force reloading /keys from the exchange ASAP; 
note
- *        that IF the forced reload fails, it is possible @a fc won't be 
called at all
- *        until a /keys download succeeds; only use #GNUNET_YES if a new /keys 
request
- *        is mandatory. If the force reload request is not allowed due to our 
rate limiting,
- *        then @a fc will be called immediately with the existing /keys data
+ * @param force_reload set to true to download /wire again even if we already 
have
+ *                     an answer (used if the answer might be stale).
  * @param fc function to call with the handles for the exchange
  * @param fc_cls closure for @a fc
  */
 struct TMH_EXCHANGES_FindOperation *
 TMH_EXCHANGES_find_exchange (const char *chosen_exchange,
-                             const char *wire_method,
-                             int force_reload,
+                             bool force_reload,
                              TMH_EXCHANGES_FindContinuation fc,
                              void *fc_cls);
 
 
+/**
+ * Lookup current wire fee by @a exchange_url and
+ * @a wire_method.
+ *
+ * @param exchange_url base URL of the exchange
+ * @param wire_method wire method to lookup fee by
+ * @param[out] wire_fee set to the wire fee
+ * @return #GNUNET_OK on success
+ *         #GNUNET_NO if @a wire_method is not supported
+ *         #GNUNET_SYSERR if @a exchange_url did not yet respond properly to 
our /wire request
+ */
+enum GNUNET_GenericReturnValue
+TMH_EXCHANGES_lookup_wire_fee (const char *exchange_url,
+                               const char *wire_method,
+                               struct TALER_Amount *wire_fee);
+
+
 /**
  * Abort pending find operation.
  *
diff --git a/src/backend/taler-merchant-httpd_helper.c 
b/src/backend/taler-merchant-httpd_helper.c
index 981f5937..0e026a87 100644
--- a/src/backend/taler-merchant-httpd_helper.c
+++ b/src/backend/taler-merchant-httpd_helper.c
@@ -755,3 +755,103 @@ TMH_trigger_webhook (const char *instance,
     return qs;
   return t.rv;
 }
+
+
+/**
+ * Closure for #add_matching_account().
+ */
+struct ExchangeMatchContext
+{
+  /**
+   * Wire method to match, NULL for all.
+   */
+  const char *wire_method;
+
+  /**
+   * Array of accounts to return.
+   */
+  json_t *accounts;
+};
+
+
+/**
+ * If the given account is feasible, add it to the array
+ * of accounts we return.
+ *
+ * @param cls a `struct PostReserveContext`
+ * @param payto_uri URI of the account
+ * @param conversion_url URL of a conversion service
+ * @param debit_restrictions restrictions for debits from account
+ * @param credit_restrictions restrictions for credits to account
+ * @param master_sig signature affirming the account
+ */
+static void
+add_matching_account (void *cls,
+                      const char *payto_uri,
+                      const char *conversion_url,
+                      const json_t *debit_restrictions,
+                      const json_t *credit_restrictions,
+                      const struct TALER_MasterSignatureP *master_sig)
+{
+  struct ExchangeMatchContext *rc = cls;
+  char *method;
+
+  method = TALER_payto_get_method (payto_uri);
+  if ( (NULL == rc->wire_method) ||
+       (0 == strcmp (method,
+                     rc->wire_method)) )
+  {
+    json_t *acc;
+
+    acc = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_string ("payto_uri",
+                               payto_uri),
+      GNUNET_JSON_pack_data_auto ("master_sig",
+                                  master_sig),
+      GNUNET_JSON_pack_allow_null (
+        GNUNET_JSON_pack_string ("conversion_url",
+                                 conversion_url)),
+      GNUNET_JSON_pack_array_incref ("credit_restrictions",
+                                     (json_t *) credit_restrictions),
+      GNUNET_JSON_pack_array_incref ("debit_restrictions",
+                                     (json_t *) debit_restrictions)
+      );
+    GNUNET_assert (0 ==
+                   json_array_append_new (rc->accounts,
+                                          acc));
+  }
+  GNUNET_free (method);
+}
+
+
+/**
+ * Return JSON array with all of the exchange accounts
+ * that support the given @a wire_method.
+ *
+ * @param master_pub master public key to match exchange by
+ * @param wire_method NULL for any
+ * @return JSON array with information about all matching accounts
+ */
+json_t *
+TMH_exchange_accounts_by_method (
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const char *wire_method)
+{
+  struct ExchangeMatchContext emc = {
+    .wire_method = wire_method,
+    .accounts = json_array ()
+  };
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_assert (NULL != emc.accounts);
+  qs = TMH_db->select_accounts_by_exchange (TMH_db->cls,
+                                            master_pub,
+                                            &add_matching_account,
+                                            &emc);
+  if (qs < 0)
+  {
+    json_decref (emc.accounts);
+    return NULL;
+  }
+  return emc.accounts;
+}
diff --git a/src/backend/taler-merchant-httpd_helper.h 
b/src/backend/taler-merchant-httpd_helper.h
index 14156fce..3b64c04b 100644
--- a/src/backend/taler-merchant-httpd_helper.h
+++ b/src/backend/taler-merchant-httpd_helper.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2021 Taler Systems SA
+  Copyright (C) 2021-2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free 
Software
@@ -13,7 +13,6 @@
   You should have received a copy of the GNU Affero General Public License 
along with
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
-
 /**
  * @file taler-merchant-httpd_helper.h
  * @brief helpers for shared logic
@@ -188,4 +187,18 @@ TMH_trigger_webhook (const char *instance,
                      const json_t *args);
 
 
+/**
+ * Return JSON array with all of the exchange accounts
+ * that support the given @a wire_method.
+ *
+ * @param master_pub master public key to match exchange by
+ * @param wire_method NULL for any
+ * @return JSON array with information about all matching accounts
+ */
+json_t *
+TMH_exchange_accounts_by_method (
+  const struct TALER_MasterPublicKeyP *master_pub,
+  const char *wire_method);
+
+
 #endif
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
index d0fcfbc0..9ab10939 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
@@ -468,30 +468,23 @@ find_next_exchange (struct AbortContext *ac);
  * passed back to the wallet).
  *
  * @param cls closure
- * @param hr HTTP response data
- * @param sign_key exchange key used to sign @a obj, or NULL
- * @param signature the actual signature, or NULL on error
+ * @param rr response data
  */
 static void
 refund_cb (void *cls,
-           const struct TALER_EXCHANGE_HttpResponse *hr,
-           const struct TALER_ExchangePublicKeyP *sign_key,
-           const struct TALER_ExchangeSignatureP *signature)
+           const struct TALER_EXCHANGE_RefundResponse *rr)
 {
   struct RefundDetails *rd = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
   struct AbortContext *ac = rd->ac;
 
-  (void) sign_key;
-  (void) signature;
   rd->rh = NULL;
   rd->http_status = hr->http_status;
   rd->exchange_reply = json_incref ((json_t*) hr->reply);
   if (MHD_HTTP_OK == hr->http_status)
   {
-    GNUNET_assert (NULL != sign_key);
-    GNUNET_assert (NULL != signature);
-    rd->exchange_pub = *sign_key;
-    rd->exchange_sig = *signature;
+    rd->exchange_pub = rr->details.ok.exchange_pub;
+    rd->exchange_sig = rr->details.ok.exchange_sig;
   }
   ac->pending_at_ce--;
   if (0 == ac->pending_at_ce)
@@ -504,10 +497,7 @@ refund_cb (void *cls,
  *
  * @param cls the `struct AbortContext`
  * @param hr HTTP response details
- * @param payto_uri payto://-URI of the exchange
  * @param exchange_handle NULL if exchange was not found to be acceptable
- * @param wire_fee current applicable fee for dealing with @a exchange_handle,
- *        NULL if not available
  * @param exchange_trusted true if this exchange is
  *        trusted by config
  */
@@ -515,13 +505,10 @@ static void
 process_abort_with_exchange (void *cls,
                              const struct TALER_EXCHANGE_HttpResponse *hr,
                              struct TALER_EXCHANGE_Handle *exchange_handle,
-                             const char *payto_uri,
-                             const struct TALER_Amount *wire_fee,
                              bool exchange_trusted)
 {
   struct AbortContext *ac = cls;
 
-  (void) payto_uri;
   (void) exchange_trusted;
   ac->fo = NULL;
   GNUNET_assert (GNUNET_YES == ac->suspended);
@@ -613,8 +600,7 @@ find_next_exchange (struct AbortContext *ac)
     {
       ac->current_exchange = rdi->exchange_url;
       ac->fo = TMH_EXCHANGES_find_exchange (ac->current_exchange,
-                                            NULL,
-                                            GNUNET_NO,
+                                            false,
                                             &process_abort_with_exchange,
                                             ac);
       if (NULL == ac->fo)
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index df9bd21e..afec3b25 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -805,9 +805,6 @@ deposit_get_callback (
  * @param cls the `struct KycContext`
  * @param hr HTTP response details
  * @param exchange_handle NULL if exchange was not found to be acceptable
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable fee for dealing with @a exchange_handle,
- *        NULL if not available
  * @param exchange_trusted true if this exchange is
  *        trusted by config
  */
@@ -816,8 +813,6 @@ process_kyc_with_exchange (
   void *cls,
   const struct TALER_EXCHANGE_HttpResponse *hr,
   struct TALER_EXCHANGE_Handle *exchange_handle,
-  const char *payto_uri,
-  const struct TALER_Amount *wire_fee,
   bool exchange_trusted)
 {
   struct KycContext *kc = cls;
@@ -948,8 +943,7 @@ check_kyc (struct PayContext *pc,
                                kc_tail,
                                kc);
   kc->fo = TMH_EXCHANGES_find_exchange (kc->exchange_url,
-                                        NULL,
-                                        GNUNET_NO,
+                                        false,
                                         &process_kyc_with_exchange,
                                         kc);
   if (NULL == kc->fo)
@@ -1013,11 +1007,11 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg,
          it is possible to over-pay if two wallets literally make a concurrent
          payment, as the earlier check for 'paid' is not in the same 
transaction
          scope as this 'insert' operation. */
-      GNUNET_assert (j < dr->details.success.num_signatures);
+      GNUNET_assert (j < dr->details.ok.num_signatures);
       qs = TMH_db->insert_deposit (
         TMH_db->cls,
         pc->hc->instance->settings.id,
-        dr->details.success.deposit_timestamp,
+        dr->details.ok.deposit_timestamp,
         &pc->h_contract_terms,
         &dc->cdd.coin_pub,
         dc->exchange_url,
@@ -1026,8 +1020,8 @@ handle_batch_deposit_ok (struct ExchangeGroup *eg,
         &dc->refund_fee,
         &dc->wire_fee,
         &pc->wm->h_wire,
-        &dr->details.success.exchange_sigs[j++],
-        dr->details.success.exchange_pub);
+        &dr->details.ok.exchange_sigs[j++],
+        dr->details.ok.exchange_pub);
       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
       {
         TMH_db->rollback (TMH_db->cls);
@@ -1181,9 +1175,6 @@ batch_deposit_cb (
  * @param cls the `struct ExchangeGroup`
  * @param hr HTTP response details
  * @param exchange_handle NULL if exchange was not found to be acceptable
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable fee for dealing with @a exchange_handle,
- *        NULL if not available
  * @param exchange_trusted true if this exchange is
  *        trusted by config
  */
@@ -1192,8 +1183,6 @@ process_pay_with_exchange (
   void *cls,
   const struct TALER_EXCHANGE_HttpResponse *hr,
   struct TALER_EXCHANGE_Handle *exchange_handle,
-  const char *payto_uri,
-  const struct TALER_Amount *wire_fee,
   bool exchange_trusted)
 {
   struct ExchangeGroup *eg = cls;
@@ -1202,7 +1191,6 @@ process_pay_with_exchange (
   const struct TALER_EXCHANGE_Keys *keys;
   unsigned int group_size;
 
-  (void) payto_uri;
   eg->fo = NULL;
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Processing payment with exchange %s\n",
@@ -1275,8 +1263,7 @@ process_pay_with_exchange (
            Maybe the wallet has seen /keys that we missed. */
         eg->tried_force_keys = true;
         eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url,
-                                              pc->wm->wire_method,
-                                              GNUNET_YES,
+                                              true,
                                               &process_pay_with_exchange,
                                               eg);
         if (NULL != eg->fo)
@@ -1316,8 +1303,7 @@ process_pay_with_exchange (
            Maybe the wallet has seen auditors that we missed. */
         eg->tried_force_keys = true;
         eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url,
-                                              pc->wm->wire_method,
-                                              GNUNET_YES,
+                                              true,
                                               &process_pay_with_exchange,
                                               eg);
         if (NULL != eg->fo)
@@ -1439,6 +1425,7 @@ AGE_FAIL:
     for (unsigned int i = 0; i<pc->coins_cnt; i++)
     {
       struct DepositConfirmation *dc = &pc->dc[i];
+      enum GNUNET_GenericReturnValue ret;
 
       if (dc->found_in_db)
         continue;
@@ -1446,7 +1433,30 @@ AGE_FAIL:
                        eg->exchange_url))
         continue;
       cdds[i] = dc->cdd;
-      dc->wire_fee = *wire_fee;
+      ret = TMH_EXCHANGES_lookup_wire_fee (dc->exchange_url,
+                                           pc->wm->wire_method,
+                                           &dc->wire_fee);
+      if (GNUNET_OK != ret)
+      {
+        enum TALER_ErrorCode ec;
+
+        fprintf (stderr,
+                 "%d\n",
+                 ret);
+        ec = (GNUNET_NO == ret)
+          ? TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED
+          : TALER_EC_MERCHANT_GENERIC_EXCHANGE_WIRE_REQUEST_FAILED;
+        pc->pending_at_eg--;
+        GNUNET_break_op (0);
+        resume_pay_with_response (
+          pc,
+          TALER_ErrorCode_get_http_status_safe (ec),
+          TALER_MHD_MAKE_JSON_PACK (
+            TALER_JSON_pack_ec (ec),
+            GNUNET_JSON_pack_string ("wire_method",
+                                     pc->wm->wire_method)));
+        return;
+      }
     }
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Initiating batch deposit with %u coins\n",
@@ -1509,8 +1519,7 @@ start_batch_deposits (struct PayContext *pc)
     if (! have_coins)
       continue; /* no coins left to deposit at this exchange */
     eg->fo = TMH_EXCHANGES_find_exchange (eg->exchange_url,
-                                          pc->wm->wire_method,
-                                          GNUNET_NO,
+                                          false,
                                           &process_pay_with_exchange,
                                           eg);
     if (NULL == eg->fo)
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-refund.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-refund.c
index 40c89712..766c8814 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-refund.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-refund.c
@@ -386,17 +386,14 @@ notify_refund_obtained (struct PostRefundData *prd)
  * refund request to an exchange.
  *
  * @param cls a `struct CoinRefund`
- * @param hr HTTP response data
- * @param exchange_pub exchange key used to sign refund confirmation
- * @param exchange_sig exchange's signature over refund
+ * @param rr response data
  */
 static void
 refund_cb (void *cls,
-           const struct TALER_EXCHANGE_HttpResponse *hr,
-           const struct TALER_ExchangePublicKeyP *exchange_pub,
-           const struct TALER_ExchangeSignatureP *exchange_sig)
+           const struct TALER_EXCHANGE_RefundResponse *rr)
 {
   struct CoinRefund *cr = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr;
 
   cr->rh = NULL;
   cr->exchange_status = hr->http_status;
@@ -413,12 +410,12 @@ refund_cb (void *cls,
   {
     enum GNUNET_DB_QueryStatus qs;
 
-    cr->exchange_pub = *exchange_pub;
-    cr->exchange_sig = *exchange_sig;
+    cr->exchange_pub = rr->details.ok.exchange_pub;
+    cr->exchange_sig = rr->details.ok.exchange_sig;
     qs = TMH_db->insert_refund_proof (TMH_db->cls,
                                       cr->refund_serial,
-                                      exchange_sig,
-                                      exchange_pub);
+                                      &rr->details.ok.exchange_sig,
+                                      &rr->details.ok.exchange_pub);
     if (0 >= qs)
     {
       /* generally, this is relatively harmless for the merchant, but let's at
@@ -443,23 +440,17 @@ refund_cb (void *cls,
  * @param cls a `struct CoinRefund *`
  * @param hr HTTP response details
  * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
  * @param exchange_trusted true if this exchange is trusted by config
  */
 static void
 exchange_found_cb (void *cls,
                    const struct TALER_EXCHANGE_HttpResponse *hr,
                    struct TALER_EXCHANGE_Handle *eh,
-                   const char *payto_uri,
-                   const struct TALER_Amount *wire_fee,
                    bool exchange_trusted)
 {
   struct CoinRefund *cr = cls;
   struct PostRefundData *prd = cr->prd;
 
-  (void) payto_uri;
-  (void) wire_fee;
   (void) exchange_trusted;
   cr->fo = NULL;
   if (NULL == hr)
@@ -725,8 +716,7 @@ TMH_post_orders_ID_refund (const struct TMH_RequestHandler 
*rh,
       {
         /* We need to talk to the exchange */
         cr->fo = TMH_EXCHANGES_find_exchange (cr->exchange_url,
-                                              NULL,
-                                              GNUNET_NO,
+                                              false,
                                               &exchange_found_cb,
                                               cr);
       }
diff --git a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c 
b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
index 24bbba35..fb560de6 100644
--- a/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
+++ b/src/backend/taler-merchant-httpd_post-tips-ID-pickup.c
@@ -309,15 +309,14 @@ TMH_force_tip_pickup_resume ()
  * planchet operation, resume HTTP processing.
  *
  * @param cls closure with a `struct PlanchetOperation *`
- * @param hr HTTP response data
- * @param blind_sig blind signature over the coin, NULL on error
+ * @param w2r response data
  */
 static void
 withdraw_cb (void *cls,
-             const struct TALER_EXCHANGE_HttpResponse *hr,
-             const struct TALER_BlindedDenominationSignature *blind_sig)
+             const struct TALER_EXCHANGE_Withdraw2Response *w2r)
 {
   struct PlanchetOperation *po = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &w2r->hr;
   struct PickupContext *pc = po->pc;
   enum GNUNET_DB_QueryStatus qs;
 
@@ -325,7 +324,7 @@ withdraw_cb (void *cls,
                                pc->po_tail,
                                po);
   TMH_db->preflight (TMH_db->cls);
-  if (NULL == blind_sig)
+  if (MHD_HTTP_OK != hr->http_status)
   {
     GNUNET_free (po);
     stop_operations (pc);
@@ -344,7 +343,7 @@ withdraw_cb (void *cls,
   qs = TMH_db->insert_pickup_blind_signature (TMH_db->cls,
                                               &pc->pickup_id,
                                               po->offset,
-                                              blind_sig);
+                                              &w2r->details.ok.blind_sig);
   GNUNET_free (po);
   if (qs < 0)
   {
@@ -380,16 +379,12 @@ withdraw_cb (void *cls,
  * @param cls closure, with our `struct PlanchetOperation *`
  * @param hr HTTP response details
  * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
  * @param exchange_trusted true if this exchange is trusted by config
  */
 static void
 do_withdraw (void *cls,
              const struct TALER_EXCHANGE_HttpResponse *hr,
              struct TALER_EXCHANGE_Handle *eh,
-             const char *payto_uri,
-             const struct TALER_Amount *wire_fee,
              bool exchange_trusted)
 {
   struct PlanchetOperation *po = cls;
@@ -463,8 +458,7 @@ try_withdraw (struct PickupContext *pc,
   po->pd = *planchet;
   po->offset = offset;
   po->fo = TMH_EXCHANGES_find_exchange (exchange_url,
-                                        NULL,
-                                        GNUNET_NO,
+                                        false,
                                         &do_withdraw,
                                         po);
   GNUNET_assert (NULL != po->fo);
@@ -507,16 +501,12 @@ do_timeout (void *cls)
  * @param cls closure, with our `struct PickupContext *`
  * @param hr HTTP response details
  * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
  * @param exchange_trusted true if this exchange is trusted by config
  */
 static void
 compute_total_requested (void *cls,
                          const struct TALER_EXCHANGE_HttpResponse *hr,
                          struct TALER_EXCHANGE_Handle *eh,
-                         const char *payto_uri,
-                         const struct TALER_Amount *wire_fee,
                          bool exchange_trusted)
 {
   struct PickupContext *pc = cls;
@@ -796,8 +786,7 @@ TMH_post_tips_ID_pickup (const struct TMH_RequestHandler 
*rh,
                                            &do_timeout,
                                            pc);
     pc->fo = TMH_EXCHANGES_find_exchange (exchange_url,
-                                          NULL,
-                                          GNUNET_NO,
+                                          false,
                                           &compute_total_requested,
                                           pc);
     GNUNET_free (exchange_url);
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 e7ab0468..773415f8 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
@@ -494,7 +494,7 @@ exchange_check_cb (void *cls,
     {
       enum GNUNET_DB_QueryStatus qs;
 
-      if (TALER_AML_NORMAL != ks->details.success.aml_status)
+      if (TALER_AML_NORMAL != ks->details.ok.aml_status)
       {
         GNUNET_assert (
           0 ==
@@ -503,7 +503,7 @@ exchange_check_cb (void *cls,
             GNUNET_JSON_PACK (
               GNUNET_JSON_pack_uint64 (
                 "aml_status",
-                ks->details.success.aml_status),
+                ks->details.ok.aml_status),
               GNUNET_JSON_pack_string ("exchange_url",
                                        ekr->exchange_url),
               GNUNET_JSON_pack_string ("payto_uri",
@@ -514,11 +514,11 @@ exchange_check_cb (void *cls,
                                            &ekr->h_wire,
                                            ekr->exchange_url,
                                            ekr->exchange_kyc_serial,
-                                           &ks->details.success.exchange_sig,
-                                           &ks->details.success.exchange_pub,
-                                           ks->details.success.timestamp,
+                                           &ks->details.ok.exchange_sig,
+                                           &ks->details.ok.exchange_pub,
+                                           ks->details.ok.timestamp,
                                            true, /* KYC OK */
-                                           ks->details.success.aml_status);
+                                           ks->details.ok.aml_status);
       if (qs < 0)
       {
         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -651,24 +651,18 @@ exchange_check_cb (void *cls,
  * @param cls closure with our `struct ExchangeKycRequest *`
  * @param hr HTTP response details
  * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
  * @param exchange_trusted true if this exchange is trusted by config
  */
 static void
 kyc_with_exchange (void *cls,
                    const struct TALER_EXCHANGE_HttpResponse *hr,
                    struct TALER_EXCHANGE_Handle *eh,
-                   const char *payto_uri,
-                   const struct TALER_Amount *wire_fee,
                    bool exchange_trusted)
 {
   struct ExchangeKycRequest *ekr = cls;
   struct KycContext *kc = ekr->kc;
   struct TALER_PaytoHashP h_payto;
 
-  (void) payto_uri;
-  (void) wire_fee;
   (void) exchange_trusted;
   ekr->fo = NULL;
   if (MHD_HTTP_OK != hr->http_status)
@@ -755,8 +749,7 @@ kyc_status_cb (void *cls,
   ekr->last_check = last_check;
   ekr->kc = kc;
   ekr->fo = TMH_EXCHANGES_find_exchange (exchange_url,
-                                         NULL,
-                                         GNUNET_NO,
+                                         false,
                                          &kyc_with_exchange,
                                          ekr);
 }
diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c 
b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
index 87ebc44e..cff03c7e 100644
--- a/src/backend/taler-merchant-httpd_private-get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
@@ -468,7 +468,7 @@ deposit_get_cb (void *cls,
 
       qs = TMH_db->insert_deposit_to_transfer (TMH_db->cls,
                                                tq->deposit_serial,
-                                               &dr->details.success);
+                                               &dr->details.ok);
       if (qs < 0)
       {
         gorc_report (gorc,
@@ -487,7 +487,7 @@ deposit_get_cb (void *cls,
       if (0 >
           TALER_amount_add (&gorc->deposits_total,
                             &gorc->deposits_total,
-                            &dr->details.success.coin_contribution))
+                            &dr->details.ok.coin_contribution))
       {
         gorc_report (gorc,
                      
TALER_EC_MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE,
@@ -592,16 +592,12 @@ deposit_get_cb (void *cls,
  * @param cls closure with a `struct GetOrderRequestContext *`
  * @param hr HTTP response details
  * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
  * @param exchange_trusted true if this exchange is trusted by config
  */
 static void
 exchange_found_cb (void *cls,
                    const struct TALER_EXCHANGE_HttpResponse *hr,
                    struct TALER_EXCHANGE_Handle *eh,
-                   const char *payto_uri,
-                   const struct TALER_Amount *wire_fee,
                    bool exchange_trusted)
 {
   struct TransferQuery *tq = cls;
@@ -697,8 +693,7 @@ deposit_cb (void *cls,
   tq->amount_with_fee = *amount_with_fee;
   tq->deposit_fee = *deposit_fee;
   tq->fo = TMH_EXCHANGES_find_exchange (exchange_url,
-                                        NULL,
-                                        GNUNET_NO,
+                                        false,
                                         &exchange_found_cb,
                                         tq);
   if (NULL == tq->fo)
diff --git a/src/backend/taler-merchant-httpd_private-get-reserves-ID.c 
b/src/backend/taler-merchant-httpd_private-get-reserves-ID.c
index 5545b02f..80d52399 100644
--- a/src/backend/taler-merchant-httpd_private-get-reserves-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-reserves-ID.c
@@ -25,6 +25,7 @@
 #include "taler-merchant-httpd.h"
 #include "taler-merchant-httpd_mhd.h"
 #include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_helper.h"
 #include "taler-merchant-httpd_private-get-reserves-ID.h"
 
 
@@ -64,7 +65,6 @@ struct GetReserveContext
  *           picked up yet
  * @param active true if the reserve is still active (we have the private key)
  * @param exchange_url URL of the exchange, NULL if not active
- * @param payto_uri payto:// URI to fill the reserve, NULL if not active or 
already paid
  * @param tips_length length of the @a tips array
  * @param tips information about the tips created by this reserve
  */
@@ -77,13 +77,14 @@ handle_reserve_details (void *cls,
                         const struct TALER_Amount *picked_up_amount,
                         const struct TALER_Amount *committed_amount,
                         bool active,
+                        const struct TALER_MasterPublicKeyP *master_pub,
                         const char *exchange_url,
-                        const char *payto_uri,
                         unsigned int tips_length,
                         const struct TALER_MERCHANTDB_TipDetails *tips)
 {
   struct GetReserveContext *ctx = cls;
   json_t *tips_json;
+  json_t *accounts;
 
   if (NULL != tips)
   {
@@ -107,6 +108,8 @@ handle_reserve_details (void *cls,
   {
     tips_json = NULL;
   }
+  accounts = TMH_exchange_accounts_by_method (master_pub,
+                                              NULL);
   ctx->res = TALER_MHD_REPLY_JSON_PACK (
     ctx->connection,
     MHD_HTTP_OK,
@@ -131,8 +134,8 @@ handle_reserve_details (void *cls,
       GNUNET_JSON_pack_string ("exchange_url",
                                exchange_url)),
     GNUNET_JSON_pack_allow_null (
-      GNUNET_JSON_pack_string ("payto_uri",
-                               payto_uri)));
+      GNUNET_JSON_pack_array_steal ("accounts",
+                                    accounts)));
 }
 
 
diff --git 
a/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c 
b/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c
index 283321db..3ffac7f3 100644
--- a/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c
+++ b/src/backend/taler-merchant-httpd_private-post-reserves-ID-authorize-tip.c
@@ -124,9 +124,19 @@ authorize_tip (const struct TMH_RequestHandler *rh,
     taler_tip_uri = TMH_make_taler_tip_uri (connection,
                                             &tip_id,
                                             hc->instance->settings.id);
+    if (NULL == taler_tip_uri)
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_GENERIC_PARAMETER_MISSING,
+                                         "host");
+    }
     tip_status_url = TMH_make_tip_status_url (connection,
                                               &tip_id,
                                               hc->instance->settings.id);
+    GNUNET_assert (NULL != tip_status_url);
+    GNUNET_assert (! GNUNET_TIME_absolute_is_zero (expiration.abs_time));
     res = TALER_MHD_REPLY_JSON_PACK (
       connection,
       MHD_HTTP_OK,
diff --git a/src/backend/taler-merchant-httpd_private-post-reserves.c 
b/src/backend/taler-merchant-httpd_private-post-reserves.c
index 82fc865f..4001badd 100644
--- a/src/backend/taler-merchant-httpd_private-post-reserves.c
+++ b/src/backend/taler-merchant-httpd_private-post-reserves.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2021, 2022 Taler Systems SA
+  (C) 2021-2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify
   it under the terms of the GNU Affero General Public License as
@@ -16,7 +16,6 @@
   License along with TALER; see the file COPYING.  If not,
   see <http://www.gnu.org/licenses/>
 */
-
 /**
  * @file taler-merchant-httpd_private-post-reserves.c
  * @brief implementing POST /reserves request handling
@@ -25,6 +24,7 @@
 #include "platform.h"
 #include "taler-merchant-httpd_exchanges.h"
 #include "taler-merchant-httpd_private-post-reserves.h"
+#include "taler-merchant-httpd_helper.h"
 #include "taler-merchant-httpd_reserves.h"
 #include <taler/taler_json_lib.h>
 
@@ -74,9 +74,14 @@ struct PostReserveContext
   const char *exchange_url;
 
   /**
-   * URI of the exchange where the payment needs to be made to.
+   * Wire method the client wants to use for the payment.
+   */
+  const char *wire_method;
+
+  /**
+   * Array of accounts that could be used.
    */
-  char *payto_uri;
+  json_t *accounts;
 
   /**
    * Handle for contacting the exchange.
@@ -88,6 +93,12 @@ struct PostReserveContext
    */
   struct GNUNET_SCHEDULER_Task *timeout_task;
 
+  /**
+   * Master public key of the exchange matching
+   * @e exchange_url.
+   */
+  struct TALER_MasterPublicKeyP master_pub;
+
   /**
    * Initial balance of the reserve.
    */
@@ -187,7 +198,7 @@ reserve_context_cleanup (void *cls)
     rc->timeout_task = NULL;
   }
   GNUNET_assert (GNUNET_YES != rc->suspended);
-  GNUNET_free (rc->payto_uri);
+  json_decref (rc->accounts);
   GNUNET_free (rc);
 }
 
@@ -198,17 +209,13 @@ reserve_context_cleanup (void *cls)
  *
  * @param cls closure with our `struct PostReserveContext *`
  * @param hr HTTP response details
- * @param payto_uri URI of the exchange for the wire transfer, NULL on errors
  * @param eh handle to the exchange context
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
  * @param exchange_trusted true if this exchange is trusted by config
  */
 static void
 handle_exchange (void *cls,
                  const struct TALER_EXCHANGE_HttpResponse *hr,
                  struct TALER_EXCHANGE_Handle *eh,
-                 const char *payto_uri,
-                 const struct TALER_Amount *wire_fee,
                  bool exchange_trusted)
 {
   struct PostReserveContext *rc = cls;
@@ -229,42 +236,55 @@ handle_exchange (void *cls,
   {
     rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
     rc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
-    TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
+    TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
     return;
   }
   if (NULL == eh)
   {
     rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE;
     rc->http_status = MHD_HTTP_BAD_GATEWAY;
-    TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
-    return;
-  }
-  keys = TALER_EXCHANGE_get_keys (eh);
-  if (NULL == keys)
-  {
-    rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE;
-    rc->http_status = MHD_HTTP_BAD_GATEWAY;
-    TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
+    TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
     return;
   }
   if (MHD_HTTP_OK != hr->http_status)
   {
+    GNUNET_assert (TALER_EC_NONE != hr->ec);
     rc->ec = hr->ec;
     rc->http_status = hr->http_status;
-    TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
+    TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
     return;
   }
-  if (NULL == payto_uri)
+  keys = TALER_EXCHANGE_get_keys (eh);
+  if (NULL == keys)
   {
-    rc->ec = TALER_EC_MERCHANT_PRIVATE_POST_RESERVES_UNSUPPORTED_WIRE_METHOD;
-    rc->http_status = MHD_HTTP_CONFLICT;
-    TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
+    rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE;
+    rc->http_status = MHD_HTTP_BAD_GATEWAY;
+    TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
     return;
   }
+  rc->master_pub = keys->master_pub;
+  {
+    rc->accounts = TMH_exchange_accounts_by_method (
+      &keys->master_pub,
+      rc->wire_method);
+    if (NULL == rc->accounts)
+    {
+      rc->ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
+      rc->http_status = MHD_HTTP_CONFLICT;
+      TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+      return;
+    }
+    if (0 == json_array_size (rc->accounts))
+    {
+      rc->ec = TALER_EC_MERCHANT_PRIVATE_POST_RESERVES_UNSUPPORTED_WIRE_METHOD;
+      rc->http_status = MHD_HTTP_CONFLICT;
+      TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+      return;
+    }
+  }
   rc->reserve_expiration
     = GNUNET_TIME_relative_to_timestamp (keys->reserve_closing_delay);
-  rc->payto_uri = GNUNET_strdup (payto_uri);
-  TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
+  TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
 }
 
 
@@ -308,8 +328,6 @@ TMH_private_post_reserves (const struct TMH_RequestHandler 
*rh,
   GNUNET_assert (NULL != mi);
   if (NULL == rc)
   {
-    const char *wire_method;
-
     rc = GNUNET_new (struct PostReserveContext);
     rc->connection = connection;
     rc->hc = hc;
@@ -317,17 +335,18 @@ TMH_private_post_reserves (const struct 
TMH_RequestHandler *rh,
     hc->cc = &reserve_context_cleanup;
 
     {
-      enum GNUNET_GenericReturnValue res;
       struct GNUNET_JSON_Specification spec[] = {
         GNUNET_JSON_spec_string ("exchange_url",
                                  &rc->exchange_url),
         GNUNET_JSON_spec_string ("wire_method",
-                                 &wire_method),
+                                 &rc->wire_method),
         TALER_JSON_spec_amount ("initial_balance",
                                 TMH_currency,
                                 &rc->initial_balance),
         GNUNET_JSON_spec_end ()
       };
+      enum GNUNET_GenericReturnValue res;
+
       res = TALER_MHD_parse_json_data (connection,
                                        hc->request_body,
                                        spec);
@@ -337,8 +356,7 @@ TMH_private_post_reserves (const struct TMH_RequestHandler 
*rh,
                : MHD_NO;
     }
     rc->fo = TMH_EXCHANGES_find_exchange (rc->exchange_url,
-                                          wire_method,
-                                          GNUNET_NO,
+                                          false,
                                           &handle_exchange,
                                           rc);
     rc->timeout_task
@@ -354,15 +372,16 @@ TMH_private_post_reserves (const struct 
TMH_RequestHandler *rh,
   }
   if (GNUNET_SYSERR == rc->suspended)
     return MHD_NO; /* we are in shutdown */
-
-  GNUNET_assert (GNUNET_NO == rc->suspended);
-  if (NULL == rc->payto_uri)
+  if (TALER_EC_NONE != rc->ec)
   {
     return TALER_MHD_reply_with_error (connection,
                                        rc->http_status,
                                        rc->ec,
                                        NULL);
   }
+  GNUNET_assert (GNUNET_NO == rc->suspended);
+  GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+                   rc->reserve_expiration.abs_time));
   {
     struct TALER_ReservePublicKeyP reserve_pub;
     struct TALER_ReservePrivateKeyP reserve_priv;
@@ -375,8 +394,8 @@ TMH_private_post_reserves (const struct TMH_RequestHandler 
*rh,
                                  mi->settings.id,
                                  &reserve_priv,
                                  &reserve_pub,
+                                 &rc->master_pub,
                                  rc->exchange_url,
-                                 rc->payto_uri,
                                  &rc->initial_balance,
                                  rc->reserve_expiration);
     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
@@ -394,8 +413,8 @@ TMH_private_post_reserves (const struct TMH_RequestHandler 
*rh,
       MHD_HTTP_OK,
       GNUNET_JSON_pack_data_auto ("reserve_pub",
                                   &reserve_pub),
-      GNUNET_JSON_pack_string ("payto_uri",
-                               rc->payto_uri));
+      GNUNET_JSON_pack_array_incref ("accounts",
+                                     rc->accounts));
   }
 }
 
diff --git a/src/backend/taler-merchant-httpd_private-post-transfers.c 
b/src/backend/taler-merchant-httpd_private-post-transfers.c
index aa21c747..a57c5e3b 100644
--- a/src/backend/taler-merchant-httpd_private-post-transfers.c
+++ b/src/backend/taler-merchant-httpd_private-post-transfers.c
@@ -457,15 +457,14 @@ check_wire_fee (struct PostTransfersContext *ptc,
  * of the coin transactions that were combined into the wire transfer.
  *
  * @param cls closure
- * @param hr HTTP response details
- * @param td transfer data
+ * @param tgr response details
  */
 static void
 wire_transfer_cb (void *cls,
-                  const struct TALER_EXCHANGE_HttpResponse *hr,
-                  const struct TALER_EXCHANGE_TransferData *td)
+                  const struct TALER_EXCHANGE_TransfersGetResponse *tgr)
 {
   struct PostTransfersContext *ptc = cls;
+  const struct TALER_EXCHANGE_HttpResponse *hr = &tgr->hr;
   const char *instance_id = ptc->hc->instance->settings.id;
   enum GNUNET_DB_QueryStatus qs;
 
@@ -503,7 +502,7 @@ wire_transfer_cb (void *cls,
                                         ptc->exchange_url,
                                         ptc->payto_uri,
                                         &ptc->wtid,
-                                        td);
+                                        &tgr->details.ok.td);
   if (0 > qs)
   {
     /* Always report on DB error as well to enable diagnostics */
@@ -528,7 +527,7 @@ wire_transfer_cb (void *cls,
     return;
   }
   if (0 !=
-      TALER_amount_cmp (&td->total_amount,
+      TALER_amount_cmp (&tgr->details.ok.td.total_amount,
                         &ptc->amount))
   {
     resume_transfer_with_error (
@@ -553,21 +552,16 @@ wire_transfer_cb (void *cls,
  * @param cls the `struct PostTransfersContext`
  * @param hr HTTP response details
  * @param eh NULL if exchange was not found to be acceptable
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee NULL (we did not specify a wire method)
  * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
  */
 static void
 process_transfer_with_exchange (void *cls,
                                 const struct TALER_EXCHANGE_HttpResponse *hr,
                                 struct TALER_EXCHANGE_Handle *eh,
-                                const char *payto_uri,
-                                const struct TALER_Amount *wire_fee,
                                 bool exchange_trusted)
 {
   struct PostTransfersContext *ptc = cls;
 
-  (void) payto_uri;
   (void) exchange_trusted;
   ptc->fo = NULL;
   if (NULL == hr)
@@ -899,8 +893,7 @@ download (struct PostTransfersContext *ptc)
                                ptc_tail,
                                ptc);
   ptc->fo = TMH_EXCHANGES_find_exchange (ptc->exchange_url,
-                                         NULL,
-                                         GNUNET_NO,
+                                         false,
                                          &process_transfer_with_exchange,
                                          ptc);
   ptc->timeout_task
diff --git a/src/backend/taler-merchant-httpd_reserves.c 
b/src/backend/taler-merchant-httpd_reserves.c
index 50af145f..fec18440 100644
--- a/src/backend/taler-merchant-httpd_reserves.c
+++ b/src/backend/taler-merchant-httpd_reserves.c
@@ -232,16 +232,12 @@ reserve_cb (void *cls,
  * @param cls closure
  * @param hr HTTP response details
  * @param eh handle to the exchange context
- * @param payto_uri payto://-URI of the exchange
- * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if 
not available
  * @param exchange_trusted true if this exchange is trusted by config
  */
 static void
 find_cb (void *cls,
          const struct TALER_EXCHANGE_HttpResponse *hr,
          struct TALER_EXCHANGE_Handle *eh,
-         const char *payto_uri,
-         const struct TALER_Amount *wire_fee,
          bool exchange_trusted)
 {
   struct Reserve *r = cls;
@@ -277,8 +273,7 @@ try_now (void *cls)
 
   r->tt = NULL;
   r->fo = TMH_EXCHANGES_find_exchange (r->exchange_url,
-                                       NULL,
-                                       GNUNET_NO,
+                                       false,
                                        &find_cb,
                                        r);
   if (NULL == r->fo)
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
index c72ddd6f..5a3611e7 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -59,6 +59,9 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
   pg_select_open_transfers.h pg_select_open_transfers.c \
   pg_lookup_instances.h pg_lookup_instances.c \
   pg_lookup_transfers.h pg_lookup_transfers.c \
+  pg_delete_exchange_accounts.h pg_delete_exchange_accounts.c \
+  pg_select_accounts_by_exchange.h pg_select_accounts_by_exchange.c \
+  pg_insert_exchange_account.h pg_insert_exchange_account.c \
   plugin_merchantdb_postgres.c  pg_helper.h
 libtaler_plugin_merchantdb_postgres_la_LIBADD = \
   $(LTLIBINTL)
diff --git a/src/backenddb/merchant-0005.sql b/src/backenddb/merchant-0005.sql
index 8124341b..5c01e55b 100644
--- a/src/backenddb/merchant-0005.sql
+++ b/src/backenddb/merchant-0005.sql
@@ -27,6 +27,12 @@ ALTER TABLE merchant_instances
 COMMENT ON COLUMN merchant_instances.user_type
   IS 'what type of user is this (individual or business)';
 
+-- Column makes no sense for multi-account exchanges.  Instead, we should
+-- lookup the various accounts of the exchange (by the master_pub) and return
+-- all of them (with constraints).
+ALTER TABLE merchant_tip_reserve_keys
+  DROP COLUMN payto_uri,
+  ADD COLUMN master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32);
 
 ALTER TABLE merchant_transfers
   ADD COLUMN failed BOOLEAN NOT NULL DEFAULT FALSE,
@@ -55,5 +61,29 @@ COMMENT ON COLUMN merchant_accounts.credit_facade_credentials
 COMMENT ON COLUMN merchant_accounts.last_bank_serial
   IS 'Serial number of the bank of the last transaction we successfully 
imported';
 
+
+CREATE TABLE IF NOT EXISTS merchant_exchange_accounts
+  (mea_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+  ,master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)
+  ,payto_uri VARCHAR NOT NULL
+  ,conversion_url VARCHAR
+  ,debit_restrictions VARCHAR NOT NULL
+  ,credit_restrictions VARCHAR NOT NULL
+  ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+  );
+COMMENT ON TABLE merchant_exchange_accounts
+ IS 'Here we store which bank accounts the exchange uses and with which 
constraints';
+COMMENT ON COLUMN merchant_exchange_accounts.master_pub
+ IS 'Master public key of the exchange with these accounts';
+COMMENT ON COLUMN merchant_exchange_accounts.payto_uri
+ IS 'RFC 8905 URI of the exchange bank account';
+COMMENT ON COLUMN merchant_exchange_accounts.conversion_url
+ IS 'NULL if this account does not require currency conversion';
+COMMENT ON COLUMN merchant_exchange_accounts.debit_restrictions
+ IS 'JSON array with account restrictions';
+COMMENT ON COLUMN merchant_exchange_accounts.credit_restrictions
+ IS 'JSON array with account restrictions';
+
+
 -- Complete transaction
 COMMIT;
diff --git a/src/backenddb/pg_update_wirewatch_progress.c 
b/src/backenddb/pg_delete_exchange_accounts.c
similarity index 59%
copy from src/backenddb/pg_update_wirewatch_progress.c
copy to src/backenddb/pg_delete_exchange_accounts.c
index 8ffdfe70..7d8a5e48 100644
--- a/src/backenddb/pg_update_wirewatch_progress.c
+++ b/src/backenddb/pg_delete_exchange_accounts.c
@@ -14,44 +14,35 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_update_wirewatch_progress.c
- * @brief Implementation of the update_wirewatch_progress function for Postgres
+ * @file backenddb/pg_delete_exchange_accounts.c
+ * @brief Implementation of the delete_exchange_accounts function for Postgres
  * @author Christian Grothoff
  */
 #include "platform.h"
 #include <taler/taler_error_codes.h>
 #include <taler/taler_dbevents.h>
 #include <taler/taler_pq_lib.h>
-#include "pg_update_wirewatch_progress.h"
+#include "pg_delete_exchange_accounts.h"
 #include "pg_helper.h"
 
 
 enum GNUNET_DB_QueryStatus
-TMH_PG_update_wirewatch_progress (
+TMH_PG_delete_exchange_accounts (
   void *cls,
-  const char *instance,
-  const char *payto_uri,
-  uint64_t last_serial)
+  const struct TALER_MasterPublicKeyP *master_pub)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_string (instance),
-    GNUNET_PQ_query_param_string (payto_uri),
-    GNUNET_PQ_query_param_uint64 (&last_serial),
+    GNUNET_PQ_query_param_auto_from_type (master_pub),
     GNUNET_PQ_query_param_end
   };
 
-  PREPARE (pg,
-           "update_wirewatch_progress",
-           "UPDATE merchant_accounts"
-           " SET last_bank_serial=$3"
-           " WHERE payto_uri=$2"
-           "  AND merchant_serial ="
-           "   (SELECT merchant_serial"
-           "      FROM merchant_instances"
-           "      WHERE merchant_id=$1)");
   check_connection (pg);
+  PREPARE (pg,
+           "delete_exchange_accounts",
+           "DELETE FROM merchant_exchange_accounts"
+           " WHERE master_pub=$1;");
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "update_wirewatch_progress",
+                                             "delete_exchange_accounts",
                                              params);
 }
diff --git a/src/backenddb/pg_insert_account.h 
b/src/backenddb/pg_delete_exchange_accounts.h
similarity index 66%
copy from src/backenddb/pg_insert_account.h
copy to src/backenddb/pg_delete_exchange_accounts.h
index 463bc527..da9013d3 100644
--- a/src/backenddb/pg_insert_account.h
+++ b/src/backenddb/pg_delete_exchange_accounts.h
@@ -14,30 +14,29 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_insert_account.h
- * @brief implementation of the insert_account function for Postgres
+ * @file backenddb/pg_delete_exchange_accounts.h
+ * @brief implementation of the delete_exchange_accounts function for Postgres
  * @author Christian Grothoff
  */
-#ifndef PG_INSERT_ACCOUNT_H
-#define PG_INSERT_ACCOUNT_H
+#ifndef PG_DELETE_EXCHANGE_ACCOUNTS_H
+#define PG_DELETE_EXCHANGE_ACCOUNTS_H
 
 #include <taler/taler_util.h>
 #include <taler/taler_json_lib.h>
 #include "taler_merchantdb_plugin.h"
 
+
 /**
- * Insert information about an instance's account into our database.
+ * Delete information about wire accounts of an exchange. (Used when we got 
new account data.)
  *
  * @param cls closure
- * @param id identifier of the instance
- * @param account_details details about the account
- * @return database result code
+ * @param master_pub public key of the exchange
+ * @return transaction status code
  */
 enum GNUNET_DB_QueryStatus
-TMH_PG_insert_account (
+TMH_PG_delete_exchange_accounts (
   void *cls,
-  const char *id,
-  const struct TALER_MERCHANTDB_AccountDetails *account_details);
+  const struct TALER_MasterPublicKeyP *master_pub);
 
 
 #endif
diff --git a/src/backenddb/pg_update_wirewatch_progress.c 
b/src/backenddb/pg_insert_exchange_account.c
similarity index 53%
copy from src/backenddb/pg_update_wirewatch_progress.c
copy to src/backenddb/pg_insert_exchange_account.c
index 8ffdfe70..4495ccc0 100644
--- a/src/backenddb/pg_update_wirewatch_progress.c
+++ b/src/backenddb/pg_insert_exchange_account.c
@@ -14,44 +14,53 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_update_wirewatch_progress.c
- * @brief Implementation of the update_wirewatch_progress function for Postgres
+ * @file backenddb/pg_insert_exchange_account.c
+ * @brief Implementation of the insert_exchange_account function for Postgres
  * @author Christian Grothoff
  */
 #include "platform.h"
 #include <taler/taler_error_codes.h>
 #include <taler/taler_dbevents.h>
 #include <taler/taler_pq_lib.h>
-#include "pg_update_wirewatch_progress.h"
+#include "pg_insert_exchange_account.h"
 #include "pg_helper.h"
 
 
 enum GNUNET_DB_QueryStatus
-TMH_PG_update_wirewatch_progress (
+TMH_PG_insert_exchange_account (
   void *cls,
-  const char *instance,
+  const struct TALER_MasterPublicKeyP *master_pub,
   const char *payto_uri,
-  uint64_t last_serial)
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  const struct TALER_MasterSignatureP *master_sig)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_string (instance),
+    GNUNET_PQ_query_param_auto_from_type (master_pub),
     GNUNET_PQ_query_param_string (payto_uri),
-    GNUNET_PQ_query_param_uint64 (&last_serial),
+    NULL == conversion_url
+    ? GNUNET_PQ_query_param_null ()
+    : GNUNET_PQ_query_param_string (conversion_url),
+    TALER_PQ_query_param_json (debit_restrictions),
+    TALER_PQ_query_param_json (credit_restrictions),
+    GNUNET_PQ_query_param_auto_from_type (master_sig),
     GNUNET_PQ_query_param_end
   };
 
-  PREPARE (pg,
-           "update_wirewatch_progress",
-           "UPDATE merchant_accounts"
-           " SET last_bank_serial=$3"
-           " WHERE payto_uri=$2"
-           "  AND merchant_serial ="
-           "   (SELECT merchant_serial"
-           "      FROM merchant_instances"
-           "      WHERE merchant_id=$1)");
   check_connection (pg);
+  PREPARE (pg,
+           "insert_exchange_account",
+           "INSERT INTO merchant_exchange_accounts"
+           "(master_pub"
+           ",payto_uri"
+           ",conversion_url"
+           ",debit_restrictions"
+           ",credit_restrictions"
+           ",master_sig)"
+           " VALUES ($1, $2, $3, $4, $5, $6);");
   return GNUNET_PQ_eval_prepared_non_select (pg->conn,
-                                             "update_wirewatch_progress",
+                                             "insert_exchange_account",
                                              params);
 }
diff --git a/src/backenddb/pg_update_wirewatch_progress.h 
b/src/backenddb/pg_insert_exchange_account.h
similarity index 50%
copy from src/backenddb/pg_update_wirewatch_progress.h
copy to src/backenddb/pg_insert_exchange_account.h
index 0e762adc..acfcdba2 100644
--- a/src/backenddb/pg_update_wirewatch_progress.h
+++ b/src/backenddb/pg_insert_exchange_account.h
@@ -14,33 +14,38 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_update_wirewatch_progress.h
- * @brief implementation of the update_wirewatch_progress function for Postgres
+ * @file backenddb/pg_insert_exchange_account.h
+ * @brief implementation of the insert_exchange_account function for Postgres
  * @author Christian Grothoff
  */
-#ifndef PG_UPDATE_WIREWATCH_PROGRESS_H
-#define PG_UPDATE_WIREWATCH_PROGRESS_H
+#ifndef PG_INSERT_EXCHANGE_ACCOUNT_H
+#define PG_INSERT_EXCHANGE_ACCOUNT_H
 
 #include <taler/taler_util.h>
 #include <taler/taler_json_lib.h>
 #include "taler_merchantdb_plugin.h"
 
-
 /**
- * Update information about progress made by taler-merchant-wirewatch.
+ * Insert information about a wire account of an exchange.
  *
  * @param cls closure
- * @param instance name of the instance to record progress for
- * @param payto_uri bank account URI to record progress for
- * @param last_serial latest serial of a transaction that was processed
- * @return transaction status
+ * @param master_pub public key of the exchange
+ * @param payto_uri URI of the bank account
+ * @param conversion_url conversion service, NULL if there is no conversion 
required
+ * @param debit_restrictions JSON array of debit restrictions on the account
+ * @param credit_restrictions JSON array of debit restrictions on the account
+ * @param master_sig signature affirming the account of the exchange
+ * @return transaction status code
  */
 enum GNUNET_DB_QueryStatus
-TMH_PG_update_wirewatch_progress (
+TMH_PG_insert_exchange_account (
   void *cls,
-  const char *instance,
+  const struct TALER_MasterPublicKeyP *master_pub,
   const char *payto_uri,
-  uint64_t last_serial);
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  const struct TALER_MasterSignatureP *master_sig);
 
 
 #endif
diff --git a/src/backenddb/pg_select_wirewatch_accounts.c 
b/src/backenddb/pg_select_accounts_by_exchange.c
similarity index 52%
copy from src/backenddb/pg_select_wirewatch_accounts.c
copy to src/backenddb/pg_select_accounts_by_exchange.c
index f3a7e789..c7637031 100644
--- a/src/backenddb/pg_select_wirewatch_accounts.c
+++ b/src/backenddb/pg_select_accounts_by_exchange.c
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022, 2023 Taler Systems SA
+   Copyright (C) 2022 Taler Systems SA
 
    TALER is free software; you can redistribute it and/or modify it under the
    terms of the GNU General Public License as published by the Free Software
@@ -14,27 +14,27 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_select_wirewatch_accounts.c
- * @brief Implementation of the select_wirewatch_accounts function for Postgres
+ * @file backenddb/pg_select_accounts_by_exchange.c
+ * @brief Implementation of the select_accounts_by_exchange function for 
Postgres
  * @author Christian Grothoff
  */
 #include "platform.h"
 #include <taler/taler_error_codes.h>
 #include <taler/taler_dbevents.h>
 #include <taler/taler_pq_lib.h>
-#include "pg_select_wirewatch_accounts.h"
+#include "pg_select_accounts_by_exchange.h"
 #include "pg_helper.h"
 
 
 /**
- * Closure for #handle_results().
+ * Closure for #parse_accounts.
  */
-struct Context
+struct SelectAccountContext
 {
   /**
-   * Function to call with results.
+   * Function to call on each result.
    */
-  TALER_MERCHANTDB_WirewatchWorkCallback cb;
+  TALER_MERCHANTDB_ExchangeAccountCallback cb;
 
   /**
    * Closure for @e cb.
@@ -42,47 +42,46 @@ struct Context
   void *cb_cls;
 
   /**
-   * Set to true if the parsing failed.
+   * Set to true on failure.
    */
-  bool failure;
+  bool failed;
 };
 
-
 /**
  * Function to be called with the results of a SELECT statement
  * that has returned @a num_results results about accounts.
  *
- * @param cls of type `struct Context *`
+ * @param cls of type `struct SelectAccountContext *`
  * @param result the postgres result
  * @param num_results the number of results in @a result
  */
 static void
-handle_results (void *cls,
+parse_accounts (void *cls,
                 PGresult *result,
                 unsigned int num_results)
 {
-  struct Context *ctx = cls;
+  struct SelectAccountContext *ctx = cls;
 
   for (unsigned int i = 0; i < num_results; i++)
   {
-    char *instance;
     char *payto_uri;
-    char *facade_url;
-    json_t *credential;
-    uint64_t last_serial;
+    char *conversion_url = NULL;
+    json_t *debit_restrictions;
+    json_t *credit_restrictions;
+    struct TALER_MasterSignatureP master_sig;
     struct GNUNET_PQ_ResultSpec rs[] = {
-      GNUNET_PQ_result_spec_auto_from_type ("instance_id",
-                                            &instance),
+      GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+                                            &master_sig),
       GNUNET_PQ_result_spec_string ("payto_uri",
                                     &payto_uri),
-      GNUNET_PQ_result_spec_string ("credit_facade_url",
-                                    &facade_url),
       GNUNET_PQ_result_spec_allow_null (
-        TALER_PQ_result_spec_json ("credit_facade_credentials",
-                                   &credential),
+        GNUNET_PQ_result_spec_string ("conversion_url",
+                                      &conversion_url),
         NULL),
-      GNUNET_PQ_result_spec_uint64 ("last_bank_serial",
-                                    &last_serial),
+      TALER_PQ_result_spec_json ("debit_restrictions",
+                                 &debit_restrictions),
+      TALER_PQ_result_spec_json ("credit_restrictions",
+                                 &credit_restrictions),
       GNUNET_PQ_result_spec_end
     };
 
@@ -92,56 +91,56 @@ handle_results (void *cls,
                                   i))
     {
       GNUNET_break (0);
-      ctx->failure = true;
+      ctx->failed = true;
       return;
     }
     ctx->cb (ctx->cb_cls,
-             instance,
              payto_uri,
-             facade_url,
-             credential,
-             last_serial);
+             conversion_url,
+             debit_restrictions,
+             credit_restrictions,
+             &master_sig);
     GNUNET_PQ_cleanup_result (rs);
   }
 }
 
 
 enum GNUNET_DB_QueryStatus
-TMH_PG_select_wirewatch_accounts (
+TMH_PG_select_accounts_by_exchange (
   void *cls,
-  TALER_MERCHANTDB_WirewatchWorkCallback cb,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  TALER_MERCHANTDB_ExchangeAccountCallback cb,
   void *cb_cls)
 {
   struct PostgresClosure *pg = cls;
-  struct Context ctx = {
+  struct SelectAccountContext ctx = {
     .cb = cb,
     .cb_cls = cb_cls
   };
   struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (master_pub),
     GNUNET_PQ_query_param_end
   };
   enum GNUNET_DB_QueryStatus qs;
 
+  check_connection (pg);
   PREPARE (pg,
-           "select_wirewatch_progress",
+           "select_exchange_accounts",
            "SELECT"
-           " last_bank_serial"
-           ",instance_id"
-           ",payto_uri"
-           ",credit_facade_url"
-           ",credit_facade_credentials"
-           " FROM merchant_accounts"
-           " JOIN merchant_instances"
-           "   USING (merchant_serial)"
-           " WHERE active"
-           "   AND credit_facade_url IS NOT NULL");
-  check_connection (pg);
-  qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
-                                             "select_wirewatch_progress",
-                                             params,
-                                             &handle_results,
-                                             &ctx);
-  if (ctx.failure)
+           " payto_uri"
+           ",conversion_url"
+           ",debit_restrictions"
+           ",credit_restrictions"
+           ",master_sig"
+           " FROM merchant_exchange_accounts"
+           " WHERE master_pub=$1;");
+  qs = GNUNET_PQ_eval_prepared_multi_select (
+    pg->conn,
+    "select_exchange_accounts",
+    params,
+    &parse_accounts,
+    &ctx);
+  if (ctx.failed)
     return GNUNET_DB_STATUS_HARD_ERROR;
   return qs;
 }
diff --git a/src/backenddb/pg_select_wirewatch_accounts.h 
b/src/backenddb/pg_select_accounts_by_exchange.h
similarity index 64%
copy from src/backenddb/pg_select_wirewatch_accounts.h
copy to src/backenddb/pg_select_accounts_by_exchange.h
index cff263d3..e22c1601 100644
--- a/src/backenddb/pg_select_wirewatch_accounts.h
+++ b/src/backenddb/pg_select_accounts_by_exchange.h
@@ -14,12 +14,12 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file backenddb/pg_select_wirewatch_accounts.h
- * @brief implementation of the select_wirewatch_accounts function for Postgres
+ * @file backenddb/pg_select_accounts_by_exchange.h
+ * @brief implementation of the select_accounts_by_exchange function for 
Postgres
  * @author Christian Grothoff
  */
-#ifndef PG_SELECT_WIREWATCH_ACCOUNTS_H
-#define PG_SELECT_WIREWATCH_ACCOUNTS_H
+#ifndef PG_SELECT_ACCOUNTS_BY_EXCHANGE_H
+#define PG_SELECT_ACCOUNTS_BY_EXCHANGE_H
 
 #include <taler/taler_util.h>
 #include <taler/taler_json_lib.h>
@@ -27,18 +27,19 @@
 
 
 /**
- * Select information about progress made by taler-merchant-wirewatch.
+ * Return information about wire accounts of an exchange.
  *
  * @param cls closure
- * @param cb function to call with results
+ * @param master_pub public key of the exchange
+ * @param cb function to call on each account
  * @param cb_cls closure for @a cb
- * @return transaction status
+ * @return transaction status code
  */
 enum GNUNET_DB_QueryStatus
-TMH_PG_select_wirewatch_accounts (
+TMH_PG_select_accounts_by_exchange (
   void *cls,
-  TALER_MERCHANTDB_WirewatchWorkCallback cb,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  TALER_MERCHANTDB_ExchangeAccountCallback cb,
   void *cb_cls);
 
-
 #endif
diff --git a/src/backenddb/plugin_merchantdb_postgres.c 
b/src/backenddb/plugin_merchantdb_postgres.c
index 6040c9af..c7ffe2a5 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -37,6 +37,9 @@
 #include "pg_update_wirewatch_progress.h"
 #include "pg_select_wirewatch_accounts.h"
 #include "pg_select_open_transfers.h"
+#include "pg_delete_exchange_accounts.h"
+#include "pg_select_accounts_by_exchange.h"
+#include "pg_insert_exchange_account.h"
 
 
 /**
@@ -4553,8 +4556,8 @@ postgres_store_wire_fee_by_exchange (
  * @param instance_id which instance is the reserve tied to
  * @param reserve_priv which reserve is topped up or created
  * @param reserve_pub which reserve is topped up or created
+ * @param master_pub master public key of the exchange
  * @param exchange_url what URL is the exchange reachable at where the reserve 
is located
- * @param payto_uri URI to use to fund the reserve
  * @param initial_balance how much money will be added to the reserve
  * @param expiration when does the reserve expire?
  * @return transaction status, usually
@@ -4565,8 +4568,8 @@ postgres_insert_reserve (void *cls,
                          const char *instance_id,
                          const struct TALER_ReservePrivateKeyP *reserve_priv,
                          const struct TALER_ReservePublicKeyP *reserve_pub,
+                         const struct TALER_MasterPublicKeyP *master_pub,
                          const char *exchange_url,
-                         const char *payto_uri,
                          const struct TALER_Amount *initial_balance,
                          struct GNUNET_TIME_Timestamp expiration)
 {
@@ -4618,7 +4621,7 @@ RETRY:
       GNUNET_PQ_query_param_auto_from_type (reserve_pub),
       GNUNET_PQ_query_param_auto_from_type (reserve_priv),
       GNUNET_PQ_query_param_string (exchange_url),
-      GNUNET_PQ_query_param_string (payto_uri),
+      GNUNET_PQ_query_param_auto_from_type (reserve_pub),
       GNUNET_PQ_query_param_end
     };
 
@@ -5084,9 +5087,9 @@ postgres_lookup_reserve (void *cls,
   struct TALER_Amount exchange_initial_balance;
   struct TALER_Amount pickup_amount;
   struct TALER_Amount committed_amount;
-  uint8_t active;
+  struct TALER_MasterPublicKeyP master_pub;
+  bool active;
   char *exchange_url = NULL;
-  char *payto_uri = NULL;
   struct GNUNET_PQ_ResultSpec rs[] = {
     GNUNET_PQ_result_spec_timestamp ("creation_time",
                                      &creation_time),
@@ -5100,16 +5103,14 @@ postgres_lookup_reserve (void *cls,
                                  &pickup_amount),
     TALER_PQ_RESULT_SPEC_AMOUNT ("tips_committed",
                                  &committed_amount),
-    GNUNET_PQ_result_spec_auto_from_type ("active",
-                                          &active),
+    GNUNET_PQ_result_spec_auto_from_type ("master_pub",
+                                          &master_pub),
+    GNUNET_PQ_result_spec_bool ("active",
+                                &active),
     GNUNET_PQ_result_spec_allow_null (
       GNUNET_PQ_result_spec_string ("exchange_url",
                                     &exchange_url),
       NULL),
-    GNUNET_PQ_result_spec_allow_null (
-      GNUNET_PQ_result_spec_string ("payto_uri",
-                                    &payto_uri),
-      NULL),
     GNUNET_PQ_result_spec_end
   };
   enum GNUNET_DB_QueryStatus qs;
@@ -5130,9 +5131,9 @@ postgres_lookup_reserve (void *cls,
         &exchange_initial_balance,
         &pickup_amount,
         &committed_amount,
-        (0 != active),
+        active,
+        &master_pub,
         exchange_url,
-        payto_uri,
         0,
         NULL);
     GNUNET_PQ_cleanup_result (rs);
@@ -5155,9 +5156,9 @@ postgres_lookup_reserve (void *cls,
         &exchange_initial_balance,
         &pickup_amount,
         &committed_amount,
-        0 != active,
+        active,
+        &master_pub,
         exchange_url,
-        payto_uri,
         ltc.tips_length,
         ltc.tips);
   }
@@ -5335,7 +5336,9 @@ lookup_reserve_for_tip_cb (void *cls,
       continue;
     }
     if ( (! GNUNET_TIME_absolute_is_never (lac->expiration.abs_time)) &&
-         GNUNET_TIME_timestamp_cmp (expiration, >, lac->expiration) &&
+         GNUNET_TIME_timestamp_cmp (expiration,
+                                    >,
+                                    lac->expiration) &&
          GNUNET_TIME_relative_cmp (
            GNUNET_TIME_absolute_get_remaining (lac->expiration.abs_time),
            >,
@@ -9064,7 +9067,7 @@ postgres_connect (void *cls)
                             "(reserve_serial"
                             ",reserve_priv"
                             ",exchange_url"
-                            ",payto_uri"
+                            ",master_pub"
                             ")"
                             "SELECT reserve_serial, $3, $4, $5"
                             " FROM merchant_tip_reserves"
@@ -9123,7 +9126,7 @@ postgres_connect (void *cls)
                             ",tips_picked_up_frac"
                             ",reserve_priv IS NOT NULL AS active"
                             ",exchange_url"
-                            ",payto_uri"
+                            ",master_pub"
                             " FROM merchant_tip_reserves"
                             " FULL OUTER JOIN merchant_tip_reserve_keys USING 
(reserve_serial)"
                             " WHERE reserve_pub = $2"
@@ -9804,6 +9807,12 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
   plugin->delete_pending_webhook = &postgres_delete_pending_webhook;
   plugin->insert_pending_webhook = &postgres_insert_pending_webhook;
   plugin->update_pending_webhook = &postgres_update_pending_webhook;
+  plugin->delete_exchange_accounts
+    = &TMH_PG_delete_exchange_accounts;
+  plugin->select_accounts_by_exchange
+    = &TMH_PG_select_accounts_by_exchange;
+  plugin->insert_exchange_account
+    = &TMH_PG_insert_exchange_account;
   return plugin;
 }
 
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index 708d8c01..c678f484 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014-2021 Taler Systems SA
+  (C) 2014-2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Lesser General Public License as published by the Free 
Software
@@ -4432,9 +4432,9 @@ struct ReserveData
   const char *exchange_url;
 
   /**
-   * The payto URI
+   * The exchange master public key.
    */
-  const char *payto_uri;
+  struct TALER_MasterPublicKeyP master_pub;
 
   /**
    * The expiration date
@@ -4461,8 +4461,8 @@ test_insert_reserve (const struct InstanceData *instance,
                                                  instance->instance.id,
                                                  &reserve->reserve_priv,
                                                  &reserve->reserve_pub,
+                                                 &reserve->master_pub,
                                                  reserve->exchange_url,
-                                                 reserve->payto_uri,
                                                  &reserve->initial_amount,
                                                  reserve->expiration),
                          "Insert reserve failed\n");
@@ -4525,8 +4525,8 @@ lookup_reserve_cb (void *cls,
                    const struct TALER_Amount *picked_up_amount,
                    const struct TALER_Amount *committed_amount,
                    bool active,
+                   const struct TALER_MasterPublicKeyP *master_pub,
                    const char *exchange_url,
-                   const char *payto_uri,
                    unsigned int tips_length,
                    const struct TALER_MERCHANTDB_TipDetails *tips)
 {
@@ -4556,8 +4556,8 @@ lookup_reserve_cb (void *cls,
     cmp->result_matches = 0;
     return;
   }
-  if (0 != strcmp (payto_uri,
-                   "payto://other-uri"))
+  if (0 != GNUNET_memcmp (&cmp->reserve_to_cmp->master_pub,
+                          master_pub))
   {
     GNUNET_break (0);
     cmp->result_matches = 0;
@@ -5402,7 +5402,9 @@ pre_test_tips (struct TestTips_Closure *cls)
                  TALER_string_to_amount ("EUR:99.99",
                                          &cls->reserve.initial_amount));
   cls->reserve.exchange_url = "https://exch-url/";;
-  cls->reserve.payto_uri = "payto://other-uri";
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                              &cls->reserve.master_pub,
+                              sizeof (cls->reserve.master_pub));
   cls->reserve.expiration = GNUNET_TIME_relative_to_timestamp (
     GNUNET_TIME_UNIT_WEEKS);
 
@@ -5416,7 +5418,9 @@ pre_test_tips (struct TestTips_Closure *cls)
                  TALER_string_to_amount ("EUR:99.99",
                                          
&cls->expired_reserve.initial_amount));
   cls->expired_reserve.exchange_url = "exch-url";
-  cls->expired_reserve.payto_uri = "payto://some-uri";
+  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+                              &cls->expired_reserve.master_pub,
+                              sizeof (cls->expired_reserve.master_pub));
   cls->expired_reserve.expiration = GNUNET_TIME_UNIT_ZERO_TS;
 
   /* Tip/pickup */
diff --git a/src/include/taler_merchant_service.h 
b/src/include/taler_merchant_service.h
index 30f65a0f..1135cb38 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -3268,22 +3268,58 @@ TALER_MERCHANT_transfers_get_cancel (
 struct TALER_MERCHANT_PostReservesHandle;
 
 
-// FIXME: change signature!
+/**
+ * Response to a POST /reserves request.
+ */
+struct TALER_MERCHANT_PostReservesResponse
+{
+  /**
+   * HTTP response details.
+   */
+  struct TALER_MERCHANT_HttpResponse hr;
+
+  /**
+   * Details depending on HTTP status.
+   */
+  union
+  {
+    /**
+     * Response on #MHD_HTTP_OK.
+     */
+    struct
+    {
+      /**
+       * Public key of the created reserve.
+       */
+      struct TALER_ReservePublicKeyP reserve_pub;
+
+      /**
+       * Accounts to credit to for filling the reserve.
+       * Array of accounts of the exchange.
+       */
+      const struct TALER_EXCHANGE_WireAccount *accounts;
+
+      /**
+       * Length of @e accounts array.
+       */
+      unsigned int accounts_len;
+
+    } ok;
+  } details;
+};
+
+
 /**
  * Callbacks of this type are used to work the result of submitting a
  * POST /reserves request to a merchant
  *
  * @param cls closure
- * @param hr HTTP response details
- * @param reserve_pub public key of the created reserve, NULL on error
- * @param payto_uri where to make the payment to for filling the reserve, NULL 
on error
+ * @param prr response details
  */
 typedef void
 (*TALER_MERCHANT_PostReservesCallback) (
   void *cls,
-  const struct TALER_MERCHANT_HttpResponse *hr,
-  const struct TALER_ReservePublicKeyP *reserve_pub,
-  const char *payto_uri);
+  const struct TALER_MERCHANT_PostReservesResponse *prr);
 
 
 /**
@@ -3455,29 +3491,82 @@ struct TALER_MERCHANT_TipDetails
 };
 
 
-// FIXME: change signature!
+/**
+ * Response to a GET /reserves/$ID request.
+ */
+struct TALER_MERCHANT_ReserveGetResponse
+{
+  /**
+   * HTTP response.
+   */
+  struct TALER_MERCHANT_HttpResponse hr;
+
+  /**
+   * Details depending on HTTP status.
+   */
+  union
+  {
+
+    /**
+     * Details on #MHD_HTTP_OK.
+     */
+    struct
+    {
+
+      /**
+       * reserve summary for the reserve
+       */
+      struct TALER_MERCHANT_ReserveSummary rs;
+
+      /**
+       * URL of the exchange hosting the reserve, NULL if not @a active
+       */
+      const char *exchange_url;
+
+      /**
+       * Accounts to credit to for filling the reserve.
+       * Array of accounts of the exchange.  Empty if
+       * already filled.
+       */
+      const struct TALER_EXCHANGE_WireAccount *accounts;
+
+      /**
+       * Length of @e accounts array.
+       */
+      unsigned int accounts_len;
+
+      /**
+       * Array with details about the tips granted.
+       */
+      const struct TALER_MERCHANT_TipDetails *tips;
+
+      /**
+       * Length of the @e tips array
+       */
+      unsigned int tips_length;
+
+      /**
+       * Is this reserve active (false if it was deleted but not purged)
+       */
+      bool active;
+
+    } ok;
+
+  } details;
+
+};
+
+
 /**
  * Callback to process a GET /reserve/$RESERVE_PUB request
  *
  * @param cls closure
- * @param hr HTTP response details
- * @param rs reserve summary for the reserve, NULL on error
- * @param active is this reserve active (false if it was deleted but not 
purged)
- * @param exchange_url URL of the exchange hosting the reserve, NULL if not @a 
active
- * @param payto_uri URI to fill the reserve, NULL if not @a active or already 
filled
- * @param tips_length length of the @a reserves array
- * @param tips array with details about the tips granted, NULL on error
+ * @param rgr response details
  */
 typedef void
 (*TALER_MERCHANT_ReserveGetCallback) (
   void *cls,
-  const struct TALER_MERCHANT_HttpResponse *hr,
-  const struct TALER_MERCHANT_ReserveSummary *rs,
-  bool active,
-  const char *exchange_url,
-  const char *payto_uri,
-  unsigned int tips_length,
-  const struct TALER_MERCHANT_TipDetails tips[]);
+  const struct TALER_MERCHANT_ReserveGetResponse *rgr);
 
 
 /**
diff --git a/src/include/taler_merchant_testing_lib.h 
b/src/include/taler_merchant_testing_lib.h
index 89c90cf9..c860baf2 100644
--- a/src/include/taler_merchant_testing_lib.h
+++ b/src/include/taler_merchant_testing_lib.h
@@ -1903,11 +1903,11 @@ TALER_TESTING_cmd_checkserver2 (const char *label,
   op (paths, const char *) \
   op (payto_uris, const char *) \
   op (amounts, const struct TALER_Amount) \
-  op (urls, char *) \
-  op (http_methods, char *)   \
-  op (http_header, char *)   \
-  op (http_body, void *)   \
-  op (http_body_size, size_t)   \
+  op (urls, const char *) \
+  op (http_methods, const char *)   \
+  op (http_header, const char *)   \
+  op (http_body, const void *)   \
+  op (http_body_size, const size_t)   \
   op (planchet_secrets, const struct TALER_PlanchetMasterSecretP)
 
 
diff --git a/src/include/taler_merchantdb_plugin.h 
b/src/include/taler_merchantdb_plugin.h
index 0d191878..30609bae 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -801,6 +801,27 @@ typedef void
   bool confirmed);
 
 
+/**
+ * If the given account is feasible, add it to the array
+ * of accounts we return.
+ *
+ * @param cls closure
+ * @param payto_uri URI of the account
+ * @param conversion_url URL of a conversion service
+ * @param debit_restrictions restrictions for debits from account
+ * @param credit_restrictions restrictions for credits to account
+ * @param master_sig signature affirming the account
+ */
+typedef void
+(*TALER_MERCHANTDB_ExchangeAccountCallback) (
+  void *cls,
+  const char *payto_uri,
+  const char *conversion_url,
+  const json_t *debit_restrictions,
+  const json_t *credit_restrictions,
+  const struct TALER_MasterSignatureP *master_sig);
+
+
 /**
  * Callback with reserve details.
  *
@@ -904,8 +925,8 @@ typedef void
  * @param committed_amount total of tips that the merchant committed to, but 
that were not
  *           picked up yet
  * @param active true if the reserve is still active (we have the private key)
+ * @param master_pub master public key of the exchange
  * @param exchange_url base URL of the exchange hosting the reserve, NULL if 
not @a active
- * @param payto_uri URI to use to fund the reserve, NULL if not @a active
  * @param tips_length length of the @a tips array
  * @param tips information about the tips created by this reserve
  */
@@ -919,8 +940,8 @@ typedef void
   const struct TALER_Amount *picked_up_amount,
   const struct TALER_Amount *committed_amount,
   bool active,
+  const struct TALER_MasterPublicKeyP *master_pub,
   const char *exchange_url,
-  const char *payto_uri,
   unsigned int tips_length,
   const struct TALER_MERCHANTDB_TipDetails *tips);
 
@@ -1122,7 +1143,6 @@ struct TALER_MERCHANTDB_Plugin
    * Roll back the current transaction of a database connection.
    *
    * @param cls the `struct PostgresClosure` with the plugin-specific state
-   * @return #GNUNET_OK on success
    */
   void
   (*rollback) (void *cls);
@@ -2391,23 +2411,76 @@ struct TALER_MERCHANTDB_Plugin
    * including signature (so we have proof).
    *
    * @param cls closure
-   * @param exchange_pub public key of the exchange
+   * @param master_pub master public key of the exchange
    * @param h_wire_method hash of wire method
    * @param fees wire fees charged
    * @param start_date start of fee being used
    * @param end_date end of fee being used
-   * @param exchange_sig signature of exchange over fee structure
+   * @param master_sig signature of exchange over fee structure
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
   (*store_wire_fee_by_exchange)(
     void *cls,
-    const struct TALER_MasterPublicKeyP *exchange_pub,
+    const struct TALER_MasterPublicKeyP *master_pub,
     const struct GNUNET_HashCode *h_wire_method,
     const struct TALER_WireFeeSet *fees,
     struct GNUNET_TIME_Timestamp start_date,
     struct GNUNET_TIME_Timestamp end_date,
-    const struct TALER_MasterSignatureP *exchange_sig);
+    const struct TALER_MasterSignatureP *master_sig);
+
+
+  /**
+   * Delete information about wire accounts of an exchange. (Used when we got 
new account data.)
+   *
+   * @param cls closure
+   * @param master_pub public key of the exchange
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*delete_exchange_accounts)(
+    void *cls,
+    const struct TALER_MasterPublicKeyP *master_pub);
+
+
+  /**
+   * Return information about wire accounts of an exchange.
+   *
+   * @param cls closure
+   * @param master_pub public key of the exchange
+   * @param cb function to call on each account
+   * @param cb_cls closure for @a cb
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*select_accounts_by_exchange)(
+    void *cls,
+    const struct TALER_MasterPublicKeyP *master_pub,
+    TALER_MERCHANTDB_ExchangeAccountCallback cb,
+    void *cb_cls);
+
+
+  /**
+   * Insert information about a wire account of an exchange.
+   *
+   * @param cls closure
+   * @param master_pub public key of the exchange
+   * @param payto_uri URI of the bank account
+   * @param conversion_url conversion service, NULL if there is no conversion 
required
+   * @param debit_restrictions JSON array of debit restrictions on the account
+   * @param credit_restrictions JSON array of debit restrictions on the account
+   * @param master_sig signature affirming the account of the exchange
+   * @return transaction status code
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_exchange_account)(
+    void *cls,
+    const struct TALER_MasterPublicKeyP *master_pub,
+    const char *payto_uri,
+    const char *conversion_url,
+    const json_t *debit_restrictions,
+    const json_t *credit_restrictions,
+    const struct TALER_MasterSignatureP *master_sig);
 
 
   /**
@@ -2421,8 +2494,8 @@ struct TALER_MERCHANTDB_Plugin
    * @param instance_id which instance is the reserve tied to
    * @param reserve_priv which reserve is topped up or created
    * @param reserve_pub which reserve is topped up or created
+   * @param master_pub master public key of the exchange
    * @param exchange_url what URL is the exchange reachable at where the 
reserve is located
-   * @param payto_uri URI to fund the reserve
    * @param initial_balance how much money will be added to the reserve
    * @param expiration when does the reserve expire?
    * @return transaction status, usually
@@ -2433,8 +2506,8 @@ struct TALER_MERCHANTDB_Plugin
                     const char *instance_id,
                     const struct TALER_ReservePrivateKeyP *reserve_priv,
                     const struct TALER_ReservePublicKeyP *reserve_pub,
+                    const struct TALER_MasterPublicKeyP *master_pub,
                     const char *exchange_url,
-                    const char *payto_uri,
                     const struct TALER_Amount *initial_balance,
                     struct GNUNET_TIME_Timestamp expiration);
 
diff --git a/src/lib/merchant_api_get_reserve.c 
b/src/lib/merchant_api_get_reserve.c
index 6c970db8..3df1dc75 100644
--- a/src/lib/merchant_api_get_reserve.c
+++ b/src/lib/merchant_api_get_reserve.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2021 Taler Systems SA
+  Copyright (C) 2014-2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Lesser General Public License as published by the Free 
Software
@@ -81,49 +81,59 @@ handle_reserve_get_finished (void *cls,
 {
   struct TALER_MERCHANT_ReserveGetHandle *rgh = cls;
   const json_t *json = response;
-  struct TALER_MERCHANT_HttpResponse hr = {
-    .http_status = (unsigned int) response_code,
-    .reply = json
+  struct TALER_MERCHANT_ReserveGetResponse rgr = {
+    .hr.http_status = (unsigned int) response_code,
+    .hr.reply = json
   };
-  bool active;
 
   rgh->job = NULL;
   switch (response_code)
   {
   case 0:
-    hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    rgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     break;
   case MHD_HTTP_OK:
     {
-      struct TALER_MERCHANT_ReserveSummary rs;
-      const json_t *tips;
-      const char *exchange_url = NULL;
-      const char *payto_uri = NULL;
+      struct TALER_MERCHANT_ReserveSummary *rs
+        = &rgr.details.ok.rs;
+      const json_t *tips = NULL;
+      const json_t *accounts = NULL;
       struct GNUNET_JSON_Specification spec[] = {
         GNUNET_JSON_spec_timestamp ("creation_time",
-                                    &rs.creation_time),
+                                    &rs->creation_time),
         GNUNET_JSON_spec_timestamp ("expiration_time",
-                                    &rs.expiration_time),
+                                    &rs->expiration_time),
         GNUNET_JSON_spec_bool ("active",
-                               &active),
+                               &rgr.details.ok.active),
         GNUNET_JSON_spec_mark_optional (
-          GNUNET_JSON_spec_string ("exchange_url",
-                                   &exchange_url),
+          GNUNET_JSON_spec_array_const ("tips",
+                                        &tips),
           NULL),
         GNUNET_JSON_spec_mark_optional (
-          GNUNET_JSON_spec_string ("payto_uri",
-                                   &payto_uri),
+          GNUNET_JSON_spec_array_const ("accounts",
+                                        &accounts),
+          NULL),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_string (
+            "exchange_url",
+            &rgr.details.ok.exchange_url),
+          NULL),
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_array_const ("accounts",
+                                        &accounts),
           NULL),
         TALER_JSON_spec_amount_any ("merchant_initial_amount",
-                                    &rs.merchant_initial_amount),
+                                    &rs->merchant_initial_amount),
         TALER_JSON_spec_amount_any ("exchange_initial_amount",
-                                    &rs.exchange_initial_amount),
+                                    &rs->exchange_initial_amount),
         TALER_JSON_spec_amount_any ("pickup_amount",
-                                    &rs.pickup_amount),
+                                    &rs->pickup_amount),
         TALER_JSON_spec_amount_any ("committed_amount",
-                                    &rs.committed_amount),
+                                    &rs->committed_amount),
         GNUNET_JSON_spec_end ()
       };
+      struct TALER_EXCHANGE_WireAccount *was = NULL;
+      struct TALER_MERCHANT_TipDetails *tds = NULL;
 
       if (GNUNET_OK !=
           GNUNET_JSON_parse (json,
@@ -131,45 +141,43 @@ handle_reserve_get_finished (void *cls,
                              NULL, NULL))
       {
         GNUNET_break_op (0);
-        hr.http_status = 0;
-        hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+        rgr.hr.http_status = 0;
+        rgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
         break;
       }
 
-      tips = json_object_get (json,
-                              "tips");
-      if ((NULL == tips) ||
-          json_is_null (tips))
-      {
-        rgh->cb (rgh->cb_cls,
-                 &hr,
-                 &rs,
-                 false,
-                 NULL,
-                 NULL,
-                 0,
-                 NULL);
-        TALER_MERCHANT_reserve_get_cancel (rgh);
-        return;
-      }
-      if (! json_is_array (tips))
+      if (NULL != accounts)
       {
-        GNUNET_break_op (0);
-        hr.http_status = 0;
-        hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
-        break;
-      }
+        unsigned int accounts_len = json_array_size (accounts);
+
+        was = GNUNET_new_array (accounts_len,
+                                struct TALER_EXCHANGE_WireAccount);
+        if (GNUNET_OK !=
+            TALER_EXCHANGE_parse_accounts (NULL,
+                                           accounts,
+                                           was,
+                                           accounts_len))
+        {
+          GNUNET_break_op (0);
+          rgr.hr.http_status = 0;
+          rgr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        }
+        else
+        {
+          rgr.details.ok.accounts = was;
+          rgr.details.ok.accounts_len = accounts_len;
+        }
+      } /* end 'have accounts' */
+
+      if (NULL != tips)
       {
-        size_t tds_length;
+        size_t tds_length = json_array_size (tips);
+        bool ok = true;
         json_t *tip;
-        struct TALER_MERCHANT_TipDetails *tds;
         unsigned int i;
-        bool ok;
 
-        tds_length = json_array_size (tips);
         tds = GNUNET_new_array (tds_length,
                                 struct TALER_MERCHANT_TipDetails);
-        ok = true;
         json_array_foreach (tips, i, tip) {
           struct TALER_MERCHANT_TipDetails *td = &tds[i];
           struct GNUNET_JSON_Specification ispec[] = {
@@ -192,60 +200,56 @@ handle_reserve_get_finished (void *cls,
             break;
           }
         }
-
         if (! ok)
         {
           GNUNET_break_op (0);
-          GNUNET_free (tds);
-          hr.http_status = 0;
-          hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
-          break;
+          rgr.hr.http_status = 0;
+          rgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+        }
+        else
+        {
+          rgr.details.ok.tips = tds;
+          rgr.details.ok.tips_length = tds_length;
         }
-        rgh->cb (rgh->cb_cls,
-                 &hr,
-                 &rs,
-                 active,
-                 exchange_url,
-                 payto_uri,
-                 tds_length,
-                 tds);
-        GNUNET_free (tds);
-        TALER_MERCHANT_reserve_get_cancel (rgh);
-        return;
+      } /* end 'have tips' */
+
+      rgh->cb (rgh->cb_cls,
+               &rgr);
+      if (NULL != accounts)
+      {
+        TALER_EXCHANGE_free_accounts (was,
+                                      json_array_size (accounts));
+        GNUNET_free (was);
       }
+      GNUNET_free (tds);
+      TALER_MERCHANT_reserve_get_cancel (rgh);
+      return;
     }
   case MHD_HTTP_UNAUTHORIZED:
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
+    rgr.hr.ec = TALER_JSON_get_error_code (json);
+    rgr.hr.hint = TALER_JSON_get_error_hint (json);
     /* Nothing really to verify, merchant says we need to authenticate. */
     break;
   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     /* Server had an internal issue; we should retry, but this API
        leaves this to the application */
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
+    rgr.hr.ec = TALER_JSON_get_error_code (json);
+    rgr.hr.hint = TALER_JSON_get_error_hint (json);
     break;
   default:
     /* unexpected response code */
     GNUNET_break_op (0);
     TALER_MERCHANT_parse_error_details_ (json,
                                          response_code,
-                                         &hr);
+                                         &rgr.hr);
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Unexpected response code %u/%d\n",
                 (unsigned int) response_code,
-                (int) hr.ec);
-    response_code = 0;
+                (int) rgr.hr.ec);
     break;
   }
   rgh->cb (rgh->cb_cls,
-           &hr,
-           NULL,
-           false,
-           NULL,
-           NULL,
-           0,
-           NULL);
+           &rgr);
   TALER_MERCHANT_reserve_get_cancel (rgh);
 }
 
diff --git a/src/lib/merchant_api_post_order_pay.c 
b/src/lib/merchant_api_post_order_pay.c
index 3923db21..c22e9ba5 100644
--- a/src/lib/merchant_api_post_order_pay.c
+++ b/src/lib/merchant_api_post_order_pay.c
@@ -190,27 +190,25 @@ check_conflict (struct TALER_MERCHANT_OrderPayHandle *oph,
  * validate the conflict error.
  *
  * @param cls a `struct TALER_MERCHANT_OrderPayHandle`
- * @param ehr reply from the exchange
- * @param keys the key structure
- * @param compat protocol compatibility indication
+ * @param kr reply from the exchange
  */
 static void
 cert_cb (void *cls,
-         const struct TALER_EXCHANGE_HttpResponse *ehr,
-         const struct TALER_EXCHANGE_Keys *keys,
-         enum TALER_EXCHANGE_VersionCompatibility compat)
+         const struct TALER_EXCHANGE_KeysResponse *kr)
 {
   struct TALER_MERCHANT_OrderPayHandle *oph = cls;
+  const struct TALER_EXCHANGE_HttpResponse *ehr = &kr->hr;
 
-  if (TALER_EXCHANGE_VC_INCOMPATIBLE & compat)
+  if ( (MHD_HTTP_OK != ehr->http_status) ||
+       (NULL == kr->details.ok.keys) )
   {
     struct TALER_MERCHANT_PayResponse pr = {
       .hr.http_status = MHD_HTTP_CONFLICT,
-      .hr.exchange_http_status = 0,
-      .hr.ec = TALER_EC_WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
+      .hr.exchange_http_status = ehr->http_status,
+      .hr.ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
       .hr.reply = oph->full_reply,
       .hr.exchange_reply = ehr->reply,
-      .hr.hint = "could not check error: incompatible exchange version"
+      .hr.hint = "failed to download /keys from the exchange"
     };
 
     oph->pay_cb (oph->pay_cb_cls,
@@ -218,16 +216,15 @@ cert_cb (void *cls,
     TALER_MERCHANT_order_pay_cancel (oph);
     return;
   }
-  if ( (MHD_HTTP_OK != ehr->http_status) ||
-       (NULL == keys) )
+  if (TALER_EXCHANGE_VC_INCOMPATIBLE & kr->details.ok.compat)
   {
     struct TALER_MERCHANT_PayResponse pr = {
       .hr.http_status = MHD_HTTP_CONFLICT,
-      .hr.exchange_http_status = ehr->http_status,
-      .hr.ec = TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+      .hr.exchange_http_status = 0,
+      .hr.ec = TALER_EC_WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
       .hr.reply = oph->full_reply,
       .hr.exchange_reply = ehr->reply,
-      .hr.hint = "failed to download /keys from the exchange"
+      .hr.hint = "could not check error: incompatible exchange version"
     };
 
     oph->pay_cb (oph->pay_cb_cls,
@@ -238,7 +235,7 @@ cert_cb (void *cls,
 
   if (GNUNET_OK !=
       check_conflict (oph,
-                      keys))
+                      kr->details.ok.keys))
   {
     struct TALER_MERCHANT_PayResponse pr = {
       .hr.http_status = 0,
diff --git a/src/lib/merchant_api_post_reserves.c 
b/src/lib/merchant_api_post_reserves.c
index 65239dfb..a2822c26 100644
--- a/src/lib/merchant_api_post_reserves.c
+++ b/src/lib/merchant_api_post_reserves.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015, 2016, 2020 Taler Systems SA
+  Copyright (C) 2014-2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Lesser General Public License as published by the Free 
Software
@@ -86,26 +86,27 @@ handle_post_reserves_finished (void *cls,
 {
   struct TALER_MERCHANT_PostReservesHandle *prh = cls;
   const json_t *json = response;
-  struct TALER_MERCHANT_HttpResponse hr = {
-    .http_status = (unsigned int) response_code,
-    .reply = json
+  struct TALER_MERCHANT_PostReservesResponse prr = {
+    .hr.http_status = (unsigned int) response_code,
+    .hr.reply = json
   };
 
   prh->job = NULL;
   switch (response_code)
   {
   case 0:
-    hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+    prr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
     break;
   case MHD_HTTP_OK:
     {
-      const char *payto_uri;
-      struct TALER_ReservePublicKeyP reserve_pub;
+      const json_t *accounts;
       struct GNUNET_JSON_Specification spec[] = {
-        GNUNET_JSON_spec_fixed_auto ("reserve_pub",
-                                     &reserve_pub),
-        GNUNET_JSON_spec_string ("payto_uri",
-                                 &payto_uri),
+        GNUNET_JSON_spec_fixed_auto (
+          "reserve_pub",
+          &prr.details.ok.reserve_pub),
+        GNUNET_JSON_spec_array_const (
+          "accounts",
+          &accounts),
         GNUNET_JSON_spec_end ()
       };
 
@@ -115,16 +116,31 @@ handle_post_reserves_finished (void *cls,
                              NULL, NULL))
       {
         GNUNET_break_op (0);
-        hr.http_status = 0;
-        hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+        prr.hr.http_status = 0;
+        prr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
         break;
       }
-      else
+
       {
+        unsigned int accounts_len = json_array_size (accounts);
+        struct TALER_EXCHANGE_WireAccount was[GNUNET_NZL (accounts_len)];
+
+        prr.details.ok.accounts = was;
+        prr.details.ok.accounts_len = accounts_len;
+        if (GNUNET_OK !=
+            TALER_EXCHANGE_parse_accounts (NULL,
+                                           accounts,
+                                           was,
+                                           accounts_len))
+        {
+          GNUNET_break_op (0);
+          prr.hr.http_status = 0;
+          prr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
+        }
         prh->cb (prh->cb_cls,
-                 &hr,
-                 &reserve_pub,
-                 payto_uri);
+                 &prr);
+        TALER_EXCHANGE_free_accounts (was,
+                                      accounts_len);
         GNUNET_JSON_parse_free (spec);
         TALER_MERCHANT_reserves_post_cancel (prh);
         return;
@@ -133,8 +149,8 @@ handle_post_reserves_finished (void *cls,
   case MHD_HTTP_ACCEPTED:
     break;
   case MHD_HTTP_UNAUTHORIZED:
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
+    prr.hr.ec = TALER_JSON_get_error_code (json);
+    prr.hr.hint = TALER_JSON_get_error_hint (json);
     /* Nothing really to verify, merchant says we need to authenticate. */
     break;
   case MHD_HTTP_NOT_FOUND:
@@ -142,31 +158,29 @@ handle_post_reserves_finished (void *cls,
        happen, we should pass the JSON reply to the application */
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Did not find any data\n");
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
+    prr.hr.ec = TALER_JSON_get_error_code (json);
+    prr.hr.hint = TALER_JSON_get_error_hint (json);
     break;
   case MHD_HTTP_INTERNAL_SERVER_ERROR:
     /* Server had an internal issue; we should retry, but this API
        leaves this to the application */
-    hr.ec = TALER_JSON_get_error_code (json);
-    hr.hint = TALER_JSON_get_error_hint (json);
+    prr.hr.ec = TALER_JSON_get_error_code (json);
+    prr.hr.hint = TALER_JSON_get_error_hint (json);
     break;
   default:
     /* unexpected response code */
     GNUNET_break_op (0);
     TALER_MERCHANT_parse_error_details_ (json,
                                          response_code,
-                                         &hr);
+                                         &prr.hr);
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Unexpected response code %u/%d\n",
                 (unsigned int) response_code,
-                (int) hr.ec);
+                (int) prr.hr.ec);
     break;
   }
   prh->cb (prh->cb_cls,
-           &hr,
-           NULL,
-           NULL);
+           &prr);
   TALER_MERCHANT_reserves_post_cancel (prh);
 }
 
diff --git a/src/lib/merchant_api_tip_pickup.c 
b/src/lib/merchant_api_tip_pickup.c
index 593efa43..ba1256b1 100644
--- a/src/lib/merchant_api_tip_pickup.c
+++ b/src/lib/merchant_api_tip_pickup.c
@@ -312,7 +312,7 @@ csr_cb (void *cls,
     {
       struct TALER_EXCHANGE_PrivateCoinDetails *pcd = &tp->pcds[pd->off];
 
-      pcd->exchange_vals = csrr->details.success.alg_values;
+      pcd->exchange_vals = csrr->details.ok.alg_values;
     }
     if (0 != tp->csr_active)
       return;
diff --git a/src/merchant-tools/taler-merchant-setup-reserve.c 
b/src/merchant-tools/taler-merchant-setup-reserve.c
index 1ed50530..46888171 100644
--- a/src/merchant-tools/taler-merchant-setup-reserve.c
+++ b/src/merchant-tools/taler-merchant-setup-reserve.c
@@ -156,46 +156,81 @@ do_request (void *cls);
  * POST /reserves request to a merchant
  *
  * @param cls closure
- * @param hr HTTP response details
- * @param reserve_pub public key of the created reserve, NULL on error
- * @param payto_uri where to make the payment to for filling the reserve, NULL 
on error
+ * @param prr response details
  */
 static void
 result_cb (void *cls,
-           const struct TALER_MERCHANT_HttpResponse *hr,
-           const struct TALER_ReservePublicKeyP *reserve_pub,
-           const char *payto_uri)
+           const struct TALER_MERCHANT_PostReservesResponse *prr)
 {
   (void) cls;
   prh = NULL;
-  switch (hr->http_status)
+  switch (prr->hr.http_status)
   {
   case MHD_HTTP_OK:
     {
-      char res_str[sizeof (*reserve_pub) * 2 + 1];
+      char res_str[sizeof (prr->details.ok.reserve_pub) * 2 + 1];
 
-      GNUNET_STRINGS_data_to_string (reserve_pub,
-                                     sizeof (*reserve_pub),
+      GNUNET_STRINGS_data_to_string (&prr->details.ok.reserve_pub,
+                                     sizeof (prr->details.ok.reserve_pub),
                                      res_str,
                                      sizeof (res_str));
-      if (NULL != strchr (payto_uri, '?'))
-        fprintf (stdout,
-                 "%s&message=%s\n",
-                 payto_uri,
-                 res_str);
-      else
-        fprintf (stdout,
-                 "%s?message=%s\n",
-                 payto_uri,
-                 res_str);
+      for (unsigned int i = 0; i<prr->details.ok.accounts_len; i++)
+      {
+        const struct TALER_EXCHANGE_WireAccount *wa
+          = &prr->details.ok.accounts[i];
+        const char *payto_uri = wa->payto_uri;
+        bool skip = false;
+
+        for (unsigned int j = 0; j<wa->credit_restrictions_length; j++)
+          if (TALER_EXCHANGE_AR_DENY ==
+              wa->credit_restrictions[j].type)
+            skip = true;
+        if (skip)
+          continue;
+        if (NULL != strchr (payto_uri, '?'))
+          fprintf (stdout,
+                   "%s&message=%s\n",
+                   payto_uri,
+                   res_str);
+        else
+          fprintf (stdout,
+                   "%s?message=%s\n",
+                   payto_uri,
+                   res_str);
+        if (NULL != wa->conversion_url)
+          fprintf (stdout,
+                   "\tConversion needed: %s\n",
+                   wa->conversion_url);
+        for (unsigned int j = 0; j<wa->credit_restrictions_length; j++)
+        {
+          const struct TALER_EXCHANGE_AccountRestriction *cr
+            = &wa->credit_restrictions[j];
+
+          switch (cr->type)
+          {
+          case TALER_EXCHANGE_AR_INVALID:
+            GNUNET_assert (0);
+            break;
+          case TALER_EXCHANGE_AR_DENY:
+            GNUNET_assert (0);
+            break;
+          case TALER_EXCHANGE_AR_REGEX:
+            fprintf (stdout,
+                     "\tCredit restriction: %s (%s)\n",
+                     cr->details.regex.human_hint,
+                     cr->details.regex.posix_egrep);
+            break;
+          }
+        }
+      }
     }
     break;
   case MHD_HTTP_CONFLICT:
     fprintf (stderr,
              "Conflict trying to setup reserve: %u/%d\nHint: %s\n",
-             hr->http_status,
-             (int) hr->ec,
-             hr->hint);
+             prr->hr.http_status,
+             (int) prr->hr.ec,
+             prr->hr.hint);
     global_ret = 1;
     break;
   case MHD_HTTP_INTERNAL_SERVER_ERROR:
@@ -212,16 +247,16 @@ result_cb (void *cls,
     }
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Merchant failed too often (%u/%d), giving up\n",
-                hr->http_status,
-                hr->ec);
+                prr->hr.http_status,
+                prr->hr.ec);
     global_ret = 1;
     break;
   default:
     fprintf (stderr,
              "Unexpected backend failure: %u/%d\nHint: %s\n",
-             hr->http_status,
-             (int) hr->ec,
-             hr->hint);
+             prr->hr.http_status,
+             (int) prr->hr.ec,
+             prr->hr.hint);
     global_ret = 1;
     break;
   }
diff --git a/src/testing/testing_api_cmd_checkserver.c 
b/src/testing/testing_api_cmd_checkserver.c
index aa75e374..39df54f6 100644
--- a/src/testing/testing_api_cmd_checkserver.c
+++ b/src/testing/testing_api_cmd_checkserver.c
@@ -79,6 +79,7 @@ struct CheckState
 
 };
 
+
 /**
  * Run the command.
  *
@@ -93,11 +94,11 @@ checkserver_run (void *cls,
 {
   struct CheckState *cs = cls;
   const struct TALER_TESTING_Command *ref;
-  char **url;
-  char **http_method;
-  char **header;
-  void **body;
-  size_t *body_size;
+  const char **url;
+  const char **http_method;
+  const char **header;
+  const void **body;
+  const size_t *body_size;
 
   (void) cmd;
   cs->is = is;
@@ -181,8 +182,10 @@ checkserver_run (void *cls,
                                               cs->index,
                                               &body_size))
     TALER_TESTING_interpreter_fail (is);
-  if ( ( (NULL == cs->expected_body) && (NULL != *body)) ||
-       ( (NULL != cs->expected_body) && (NULL == body)) ||
+  if ( ( (NULL == cs->expected_body) &&
+         (NULL != *body) ) ||
+       ( (NULL != cs->expected_body) &&
+         (NULL == body) ) ||
        ( (NULL != cs->expected_body) &&
          ( (*body_size != strlen (cs->expected_body)) ||
            (0 != memcmp (cs->expected_body,
@@ -197,7 +200,6 @@ checkserver_run (void *cls,
     TALER_TESTING_interpreter_fail (is);
     return;
   }
-
   TALER_TESTING_interpreter_next (is);
 }
 
diff --git a/src/testing/testing_api_cmd_get_reserve.c 
b/src/testing/testing_api_cmd_get_reserve.c
index ef7f67e0..db6f2562 100644
--- a/src/testing/testing_api_cmd_get_reserve.c
+++ b/src/testing/testing_api_cmd_get_reserve.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2020 Taler Systems SA
+  Copyright (C) 2020-2023 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as
@@ -75,15 +75,10 @@ struct GetReserveState
 
 static void
 get_reserve_cb (void *cls,
-                const struct TALER_MERCHANT_HttpResponse *hr,
-                const struct TALER_MERCHANT_ReserveSummary *rs,
-                bool active,
-                const char *exchange_url,
-                const char *payto_uri,
-                unsigned int tips_length,
-                const struct TALER_MERCHANT_TipDetails tips[])
+                const struct TALER_MERCHANT_ReserveGetResponse *rgr)
 {
   struct GetReserveState *grs = cls;
+  const struct TALER_MERCHANT_HttpResponse *hr = &rgr->hr;
   const struct TALER_TESTING_Command *reserve_cmd;
 
   reserve_cmd = TALER_TESTING_interpreter_lookup_command (
@@ -105,6 +100,7 @@ get_reserve_cb (void *cls,
   {
   case MHD_HTTP_OK:
     {
+      const struct TALER_MERCHANT_ReserveSummary *rs = &rgr->details.ok.rs;
       const struct TALER_Amount *initial_amount;
       if (GNUNET_OK !=
           TALER_TESTING_get_trait_amount (reserve_cmd,
@@ -122,71 +118,76 @@ get_reserve_cb (void *cls,
         return;
       }
     }
-    if (tips_length != grs->tips_length)
     {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Number of tips authorized does not match\n");
-      TALER_TESTING_interpreter_fail (grs->is);
-      return;
-    }
-    for (unsigned int i = 0; i < tips_length; ++i)
-    {
-      const struct TALER_TESTING_Command *tip_cmd;
+      unsigned int tips_length = rgr->details.ok.tips_length;
+      const struct TALER_MERCHANT_TipDetails *tips = rgr->details.ok.tips;
 
-      tip_cmd = TALER_TESTING_interpreter_lookup_command (grs->is,
-                                                          grs->tips[i]);
+      if (tips_length != grs->tips_length)
       {
-        const struct TALER_TipIdentifierP *tip_id;
-
-        if (GNUNET_OK !=
-            TALER_TESTING_get_trait_tip_id (tip_cmd,
-                                            &tip_id))
-          TALER_TESTING_interpreter_fail (grs->is);
+        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                    "Number of tips authorized does not match\n");
+        TALER_TESTING_interpreter_fail (grs->is);
+        return;
+      }
+      for (unsigned int i = 0; i < tips_length; ++i)
+      {
+        const struct TALER_TESTING_Command *tip_cmd;
 
-        if (0 != GNUNET_memcmp (&tips[i].tip_id,
-                                tip_id))
+        tip_cmd = TALER_TESTING_interpreter_lookup_command (grs->is,
+                                                            grs->tips[i]);
         {
-          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                      "Reserve tip id does not match\n");
-          TALER_TESTING_interpreter_fail (grs->is);
-          return;
+          const struct TALER_TipIdentifierP *tip_id;
+
+          if (GNUNET_OK !=
+              TALER_TESTING_get_trait_tip_id (tip_cmd,
+                                              &tip_id))
+            TALER_TESTING_interpreter_fail (grs->is);
+
+          if (0 != GNUNET_memcmp (&tips[i].tip_id,
+                                  tip_id))
+          {
+            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                        "Reserve tip id does not match\n");
+            TALER_TESTING_interpreter_fail (grs->is);
+            return;
+          }
         }
-      }
-      {
-        const struct TALER_Amount *total_amount;
-
-        if (GNUNET_OK !=
-            TALER_TESTING_get_trait_amount (tip_cmd,
-                                            &total_amount))
-          TALER_TESTING_interpreter_fail (grs->is);
-
-        if ((GNUNET_OK !=
-             TALER_amount_cmp_currency (&tips[i].amount,
-                                        total_amount)) ||
-            (0 != TALER_amount_cmp (&tips[i].amount,
-                                    total_amount)))
         {
-          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                      "Reserve tip amount does not match\n");
-          TALER_TESTING_interpreter_fail (grs->is);
-          return;
+          const struct TALER_Amount *total_amount;
+
+          if (GNUNET_OK !=
+              TALER_TESTING_get_trait_amount (tip_cmd,
+                                              &total_amount))
+            TALER_TESTING_interpreter_fail (grs->is);
+
+          if ((GNUNET_OK !=
+               TALER_amount_cmp_currency (&tips[i].amount,
+                                          total_amount)) ||
+              (0 != TALER_amount_cmp (&tips[i].amount,
+                                      total_amount)))
+          {
+            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                        "Reserve tip amount does not match\n");
+            TALER_TESTING_interpreter_fail (grs->is);
+            return;
+          }
         }
-      }
-      {
-        const char **reason;
-
-        if (GNUNET_OK !=
-            TALER_TESTING_get_trait_reason (tip_cmd,
-                                            &reason))
-          TALER_TESTING_interpreter_fail (grs->is);
-
-        if (0 != strcmp (tips[i].reason,
-                         *reason))
         {
-          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                      "Reserve tip reason does not match\n");
-          TALER_TESTING_interpreter_fail (grs->is);
-          return;
+          const char **reason;
+
+          if (GNUNET_OK !=
+              TALER_TESTING_get_trait_reason (tip_cmd,
+                                              &reason))
+            TALER_TESTING_interpreter_fail (grs->is);
+
+          if (0 != strcmp (tips[i].reason,
+                           *reason))
+          {
+            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                        "Reserve tip reason does not match\n");
+            TALER_TESTING_interpreter_fail (grs->is);
+            return;
+          }
         }
       }
     }
diff --git a/src/testing/testing_api_cmd_post_reserves.c 
b/src/testing/testing_api_cmd_post_reserves.c
index b2167534..fe3de9ed 100644
--- a/src/testing/testing_api_cmd_post_reserves.c
+++ b/src/testing/testing_api_cmd_post_reserves.c
@@ -79,32 +79,29 @@ struct PostReservesState
  * POST /reserves request to a merchant
  *
  * @param cls closure
- * @param hr HTTP response details
- * @param reserve_pub public key of the created reserve, NULL on error
- * @param payto_uri where to make the payment to for filling the reserve, NULL 
on error
+ * @param prr response details
  */
 static void
 post_reserves_cb (void *cls,
-                  const struct TALER_MERCHANT_HttpResponse *hr,
-                  const struct TALER_ReservePublicKeyP *reserve_pub,
-                  const char *payto_uri)
+                  const struct TALER_MERCHANT_PostReservesResponse *prr)
 {
   struct PostReservesState *prs = cls;
 
   prs->prh = NULL;
-  if (prs->http_status != hr->http_status)
+  if (prs->http_status != prr->hr.http_status)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Unexpected response code %u (%d) to command %s\n",
-                hr->http_status,
-                (int) hr->ec,
+                prr->hr.http_status,
+                (int) prr->hr.ec,
                 TALER_TESTING_interpreter_get_current_label (prs->is));
     TALER_TESTING_interpreter_fail (prs->is);
     return;
   }
-  switch (hr->http_status)
+  switch (prr->hr.http_status)
   {
   case MHD_HTTP_OK:
+    prs->reserve_pub = prr->details.ok.reserve_pub;
     break;
   case MHD_HTTP_ACCEPTED:
     break;
@@ -116,9 +113,9 @@ post_reserves_cb (void *cls,
     GNUNET_break (0);
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                 "Unhandled HTTP status %u for POST /reserves.\n",
-                hr->http_status);
+                prr->hr.http_status);
+    break;
   }
-  prs->reserve_pub = *reserve_pub;
   TALER_TESTING_interpreter_next (prs->is);
 }
 
diff --git a/src/testing/testing_api_cmd_post_transfers.c 
b/src/testing/testing_api_cmd_post_transfers.c
index 9a01d2d5..b73c6b15 100644
--- a/src/testing/testing_api_cmd_post_transfers.c
+++ b/src/testing/testing_api_cmd_post_transfers.c
@@ -404,10 +404,10 @@ debit_cb (
     TALER_TESTING_interpreter_fail (pts->is);
     return;
   }
-  for (unsigned int i = 0; i<reply->details.success.details_length; i++)
+  for (unsigned int i = 0; i<reply->details.ok.details_length; i++)
   {
     const struct TALER_BANK_DebitDetails *details
-      = &reply->details.success.details[i];
+      = &reply->details.ok.details[i];
 
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Bank reports transfer of %s to %s\n",
diff --git a/src/testing/testing_api_cmd_post_using_templates.c 
b/src/testing/testing_api_cmd_post_using_templates.c
index e0c9c2e1..38743281 100644
--- a/src/testing/testing_api_cmd_post_using_templates.c
+++ b/src/testing/testing_api_cmd_post_using_templates.c
@@ -28,7 +28,6 @@
 #include "taler_merchant_testing_lib.h"
 
 
-
 /**
  * State of a "POST /templates" CMD.
  */
@@ -80,7 +79,7 @@ struct PostUsingTemplatesState
   /**
    * Order id.
    */
-  const char *order_id;
+  char *order_id;
 
   /**
    * The order id we expect the merchant to assign (if not NULL).
@@ -163,10 +162,10 @@ struct PostUsingTemplatesState
  */
 static void
 using_claim_cb (void *cls,
-                 const struct TALER_MERCHANT_HttpResponse *hr,
-                 const json_t *contract_terms,
-                 const struct TALER_MerchantSignatureP *sig,
-                 const struct TALER_PrivateContractHashP *hash)
+                const struct TALER_MERCHANT_HttpResponse *hr,
+                const json_t *contract_terms,
+                const struct TALER_MerchantSignatureP *sig,
+                const struct TALER_PrivateContractHashP *hash)
 {
   struct PostUsingTemplatesState *tis = cls;
   struct TALER_MerchantPublicKeyP merchant_pub;
@@ -403,7 +402,7 @@ post_using_templates_traits (void *cls,
 {
   struct PostUsingTemplatesState *pts = cls;
   struct TALER_TESTING_Trait traits[] = {
-    TALER_TESTING_make_trait_order_id (&pts->order_id),
+    TALER_TESTING_make_trait_order_id ((const char **) &pts->order_id),
     TALER_TESTING_make_trait_contract_terms (pts->contract_terms),
     TALER_TESTING_make_trait_order_terms (pts->order_terms),
     TALER_TESTING_make_trait_h_contract_terms (&pts->h_contract_terms),
@@ -443,6 +442,9 @@ post_using_templates_cleanup (void *cls,
                 "POST /using-templates operation did not complete\n");
     TALER_MERCHANT_using_templates_post_cancel (tis->iph);
   }
+  json_decref (tis->order_terms);
+  json_decref (tis->contract_terms);
+  GNUNET_free (tis->order_id);
   GNUNET_free (tis);
 }
 
@@ -484,22 +486,58 @@ make_order_json (const char *using_template_id,
   struct GNUNET_TIME_Timestamp refund = refund_deadline;
   struct GNUNET_TIME_Timestamp pay = pay_deadline;
   json_t *contract_terms;
+  struct TALER_Amount tamount;
+  json_t *arr;
 
+  if (NULL != amount)
+    GNUNET_assert (GNUNET_OK ==
+                   TALER_string_to_amount (amount,
+                                           &tamount));
   /* Include required fields and some dummy objects to test forgetting. */
-  contract_terms = json_pack (
-    "{s:s, s:s?, s:s, s:s, s:o, s:o, s:s, s:[{s:s}, {s:s}, {s:s}]}",
-    "summary", "merchant-lib testcase",
-    "using_template_id", using_template_id,
-    "amount", amount,
-    "fulfillment_url", "https://example.com";,
-    "refund_deadline", GNUNET_JSON_from_timestamp (refund),
-    "pay_deadline", GNUNET_JSON_from_timestamp (pay),
-    "dummy_obj", "EUR:1.0",
-    "dummy_array", /* For testing forgetting parts of arrays */
-    "item", "speakers",
-    "item", "headphones",
-    "item", "earbuds"
-    );
+  arr = json_array ();
+  GNUNET_assert (NULL != arr);
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   arr,
+                   GNUNET_JSON_PACK (
+                     GNUNET_JSON_pack_string (
+                       "item", "speakers"))));
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   arr,
+                   GNUNET_JSON_PACK (
+                     GNUNET_JSON_pack_string (
+                       "item", "headphones"))));
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   arr,
+                   GNUNET_JSON_PACK (
+                     GNUNET_JSON_pack_string (
+                       "item", "earbuds"))));
+  contract_terms = GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_string ("summary",
+                             "merchant-lib testcase"),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string (
+        "using_template_id", using_template_id)),
+    NULL == amount
+    ? GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_string ("amount",
+                               NULL))
+    : TALER_JSON_pack_amount ("amount",
+                              &tamount),
+    GNUNET_JSON_pack_string ("fulfillment_url",
+                             "https://example.com";),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_timestamp ("refund_deadline",
+                                  refund)),
+    GNUNET_JSON_pack_allow_null (
+      GNUNET_JSON_pack_timestamp ("pay_deadline",
+                                  pay)),
+    GNUNET_JSON_pack_string ("dummy_obj",
+                             "EUR:1.0"),
+    GNUNET_JSON_pack_array_steal ("dummy_array",
+                                  arr));
   GNUNET_assert (GNUNET_OK ==
                  TALER_JSON_expand_path (contract_terms,
                                          "$.dummy_obj",
diff --git a/src/testing/testing_api_cmd_testserver.c 
b/src/testing/testing_api_cmd_testserver.c
index ee32fd77..e80dcd78 100644
--- a/src/testing/testing_api_cmd_testserver.c
+++ b/src/testing/testing_api_cmd_testserver.c
@@ -47,8 +47,15 @@ struct TestserverState
    */
   uint16_t port;
 
+  /**
+   * Array where we remember all of the requests this
+   * server answered.
+   */
   struct RequestCtx **rcs;
 
+  /**
+   * Length of the @a rcs array
+   */
   unsigned int rcs_length;
 };
 
@@ -80,6 +87,9 @@ struct RequestCtx
    */
   size_t body_size;
 
+  /**
+   * Set to true when we are done with the request.
+   */
   bool done;
 };
 
@@ -150,7 +160,7 @@ handler_cb (void *cls,
                                        "Taler-test-header");
     if (NULL != hdr)
       rc->header = GNUNET_strdup (hdr);
-    if (NULL != hdr)
+    if (NULL != url)
       rc->url = GNUNET_strdup (url);
     GNUNET_array_append (ts->rcs,
                          ts->rcs_length,
@@ -195,9 +205,6 @@ handler_cb (void *cls,
     rc->body = body;
     rc->body_size += *upload_data_size;
     *upload_data_size = 0;
-    GNUNET_array_append (ts->rcs,
-                         ts->rcs_length,
-                         rc);
     return MHD_YES;
   }
 
@@ -276,28 +283,30 @@ testserver_cleanup (void *cls,
   struct TestserverState *ser = cls;
 
   (void) cmd;
+  if (NULL != ser->mhd)
+  {
+    MHD_stop_daemon (ser->mhd);
+    ser->mhd = NULL;
+  }
   for (unsigned int i = 0; i<ser->rcs_length; i++)
   {
     struct RequestCtx *rc = ser->rcs[i];
 
+    GNUNET_assert (rc->done);
     GNUNET_free (rc->url);
     GNUNET_free (rc->http_method);
     GNUNET_free (rc->header);
     GNUNET_free (rc->body);
+    GNUNET_free (rc);
   }
   GNUNET_array_grow (ser->rcs,
                      ser->rcs_length,
                      0);
-  if (NULL != ser->mhd)
-  {
-    MHD_stop_daemon (ser->mhd);
-    ser->mhd = NULL;
-  }
   GNUNET_free (ser);
 }
 
 
-static int
+static enum GNUNET_GenericReturnValue
 traits_testserver (void *cls,
                    const void **ret,
                    const char *trait,
@@ -306,24 +315,26 @@ traits_testserver (void *cls,
   struct TestserverState *ser = cls;
 
   if (index >= ser->rcs_length)
-    return MHD_NO;
+    return GNUNET_NO;
 
   {
-    struct RequestCtx *rc = ser->rcs[index];
+    const struct RequestCtx *rc = ser->rcs[index];
     struct TALER_TESTING_Trait traits[] = {
       TALER_TESTING_make_trait_urls (index,
-                                     &rc->url),
+                                     (const char **) &rc->url),
       TALER_TESTING_make_trait_http_methods (index,
-                                             &rc->http_method),
+                                             (const char **) &rc->http_method),
       TALER_TESTING_make_trait_http_header (index,
-                                            &rc->header),
+                                            (const char **) &rc->header),
       TALER_TESTING_make_trait_http_body (index,
-                                          &rc->body),
+                                          (const void **) &rc->body),
       TALER_TESTING_make_trait_http_body_size (index,
                                                &rc->body_size),
       TALER_TESTING_trait_end (),
     };
 
+    if (! rc->done)
+      return GNUNET_NO;
     return TALER_TESTING_get_trait (traits,
                                     ret,
                                     trait,

-- 
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]