gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] 02/04: first rough cut at merchant update for #7810 (st


From: gnunet
Subject: [taler-merchant] 02/04: first rough cut at merchant update for #7810 (still with known bugs)
Date: Tue, 02 May 2023 10:57:39 +0200

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

grothoff pushed a commit to branch master
in repository merchant.

commit 583c01c6224dd317f9665e623b02a32b74fdf74a
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Mon May 1 00:09:55 2023 +0200

    first rough cut at merchant update for #7810 (still with known bugs)
---
 src/backend/taler-merchant-httpd_exchanges.c       | 541 ++++++++++++---------
 src/backend/taler-merchant-httpd_exchanges.h       |  34 +-
 src/backend/taler-merchant-httpd_helper.c          |  96 ++++
 src/backend/taler-merchant-httpd_helper.h          |  16 +-
 .../taler-merchant-httpd_post-orders-ID-abort.c    |  26 +-
 .../taler-merchant-httpd_post-orders-ID-pay.c      |  54 +-
 .../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 +-
 .../taler-merchant-httpd_private-post-reserves.c   |  84 ++--
 .../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 ++
 src/backenddb/pg_delete_exchange_accounts.c        |  48 ++
 src/backenddb/pg_delete_exchange_accounts.h        |  42 ++
 src/backenddb/pg_insert_exchange_account.c         |  66 +++
 src/backenddb/pg_insert_exchange_account.h         |  51 ++
 src/backenddb/pg_select_accounts_by_exchange.c     | 146 ++++++
 src/backenddb/pg_select_accounts_by_exchange.h     |  45 ++
 src/backenddb/plugin_merchantdb_postgres.c         |  41 +-
 src/include/taler_merchant_service.h               | 133 ++++-
 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_get_reserve.c          | 129 ++---
 src/testing/testing_api_cmd_post_reserves.c        |  21 +-
 src/testing/testing_api_cmd_post_transfers.c       |   4 +-
 33 files changed, 1495 insertions(+), 682 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_exchanges.c 
b/src/backend/taler-merchant-httpd_exchanges.c
index f0324c47..90875836 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,6 +24,10 @@
 #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.
@@ -129,11 +133,6 @@ struct FeesByWireMethod
    */
   char *wire_method;
 
-  /**
-   * Full payto URI of the exchange.
-   */
-  char *payto_uri;
-
   /**
    * Applicable fees, NULL if unknown/error.
    */
@@ -285,16 +284,11 @@ json_t *TMH_trusted_exchanges;
  *   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
+ * @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 +341,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 +537,89 @@ 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);
+          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);
+          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);
+      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;
 }
 
 
@@ -643,8 +753,6 @@ process_find_operations (struct Exchange *exchange)
       fo->fc (fo->fc_cls,
               &hr,
               exchange->conn,
-              (NULL != fbw) ? fbw->payto_uri : NULL,
-              (NULL != fbw) ? &fbw->af->fees.wire : NULL,
               exchange->trusted);
     }
     TMH_EXCHANGES_find_exchange_cancel (fo);
@@ -668,15 +776,11 @@ wire_task_cb (void *cls);
  * 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 +789,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 +797,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 +813,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 +827,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 +837,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;
@@ -754,7 +859,7 @@ handle_wire_data (void *cls,
 
                 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,
@@ -818,7 +923,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,12 +958,10 @@ 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;
 
@@ -879,23 +981,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) )
   {
@@ -921,40 +1009,22 @@ 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 +1052,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,8 +1072,7 @@ keys_mgmt_cb (void *cls,
     {
       GNUNET_break (0);
       fail_and_retry (exchange,
-                      hr,
-                      compat);
+                      &kr->hr);
       return;
     }
   }
@@ -1089,16 +1145,35 @@ return_result (void *cls)
 }
 
 
+/**
+ * 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;
+  const char *wire_method = NULL;
 
   if (NULL == merchant_curl_ctx)
   {
@@ -1109,22 +1184,7 @@ 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 */
@@ -1325,6 +1385,31 @@ accept_exchanges (void *cls,
 }
 
 
+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)
+    return GNUNET_SYSERR;
+  if (! exchange->have_wire)
+    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..b93cdb12 100644
--- a/src/backend/taler-merchant-httpd_helper.c
+++ b/src/backend/taler-merchant-httpd_helper.c
@@ -755,3 +755,99 @@ 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_allow_null (
+        GNUNET_JSON_pack_string ("conversion_url",
+                                 conversion_url)),
+      GNUNET_JSON_pack_array_incref ("credit_restrictions",
+                                     (json_t *) credit_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 930186d3..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
@@ -187,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..55e345e6 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,27 @@ 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;
+
+        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 +1516,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.c 
b/src/backend/taler-merchant-httpd_private-post-reserves.c
index 82fc865f..a6008f32 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;
@@ -222,6 +229,7 @@ handle_exchange (void *cls,
   }
   rc->suspended = GNUNET_NO;
   MHD_resume_connection (rc->connection);
+  TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
   GNUNET_CONTAINER_DLL_remove (rc_head,
                                rc_tail,
                                rc);
@@ -229,42 +237,47 @@ 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 */
     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 */
     return;
   }
   if (MHD_HTTP_OK != hr->http_status)
   {
     rc->ec = hr->ec;
     rc->http_status = hr->http_status;
-    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;
     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;
+      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;
+      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 */
 }
 
 
@@ -308,11 +321,12 @@ 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;
+    rc->accounts = json_array ();
+    GNUNET_assert (NULL != rc->accounts);
     hc->ctx = rc;
     hc->cc = &reserve_context_cleanup;
 
@@ -322,7 +336,7 @@ TMH_private_post_reserves (const struct TMH_RequestHandler 
*rh,
         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),
@@ -337,8 +351,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 +367,14 @@ 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);
   {
     struct TALER_ReservePublicKeyP reserve_pub;
     struct TALER_ReservePrivateKeyP reserve_priv;
@@ -375,8 +387,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 +406,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_steal ("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_delete_exchange_accounts.c 
b/src/backenddb/pg_delete_exchange_accounts.c
new file mode 100644
index 00000000..7d8a5e48
--- /dev/null
+++ b/src/backenddb/pg_delete_exchange_accounts.c
@@ -0,0 +1,48 @@
+/*
+   This file is part of TALER
+   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
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file 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_delete_exchange_accounts.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_exchange_accounts (
+  void *cls,
+  const struct TALER_MasterPublicKeyP *master_pub)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (master_pub),
+    GNUNET_PQ_query_param_end
+  };
+
+  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,
+                                             "delete_exchange_accounts",
+                                             params);
+}
diff --git a/src/backenddb/pg_delete_exchange_accounts.h 
b/src/backenddb/pg_delete_exchange_accounts.h
new file mode 100644
index 00000000..da9013d3
--- /dev/null
+++ b/src/backenddb/pg_delete_exchange_accounts.h
@@ -0,0 +1,42 @@
+/*
+   This file is part of TALER
+   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
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_delete_exchange_accounts.h
+ * @brief implementation of the delete_exchange_accounts function for Postgres
+ * @author Christian Grothoff
+ */
+#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"
+
+
+/**
+ * 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
+TMH_PG_delete_exchange_accounts (
+  void *cls,
+  const struct TALER_MasterPublicKeyP *master_pub);
+
+
+#endif
diff --git a/src/backenddb/pg_insert_exchange_account.c 
b/src/backenddb/pg_insert_exchange_account.c
new file mode 100644
index 00000000..4495ccc0
--- /dev/null
+++ b/src/backenddb/pg_insert_exchange_account.c
@@ -0,0 +1,66 @@
+/*
+   This file is part of TALER
+   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
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file 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_insert_exchange_account.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_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)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (master_pub),
+    GNUNET_PQ_query_param_string (payto_uri),
+    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
+  };
+
+  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,
+                                             "insert_exchange_account",
+                                             params);
+}
diff --git a/src/backenddb/pg_insert_exchange_account.h 
b/src/backenddb/pg_insert_exchange_account.h
new file mode 100644
index 00000000..acfcdba2
--- /dev/null
+++ b/src/backenddb/pg_insert_exchange_account.h
@@ -0,0 +1,51 @@
+/*
+   This file is part of TALER
+   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
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_insert_exchange_account.h
+ * @brief implementation of the insert_exchange_account function for Postgres
+ * @author Christian Grothoff
+ */
+#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"
+
+/**
+ * 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
+TMH_PG_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);
+
+
+#endif
diff --git a/src/backenddb/pg_select_accounts_by_exchange.c 
b/src/backenddb/pg_select_accounts_by_exchange.c
new file mode 100644
index 00000000..c7637031
--- /dev/null
+++ b/src/backenddb/pg_select_accounts_by_exchange.c
@@ -0,0 +1,146 @@
+/*
+   This file is part of TALER
+   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
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file 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_accounts_by_exchange.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #parse_accounts.
+ */
+struct SelectAccountContext
+{
+  /**
+   * Function to call on each result.
+   */
+  TALER_MERCHANTDB_ExchangeAccountCallback cb;
+
+  /**
+   * Closure for @e cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Set to true on 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 SelectAccountContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+parse_accounts (void *cls,
+                PGresult *result,
+                unsigned int num_results)
+{
+  struct SelectAccountContext *ctx = cls;
+
+  for (unsigned int i = 0; i < num_results; i++)
+  {
+    char *payto_uri;
+    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 ("master_sig",
+                                            &master_sig),
+      GNUNET_PQ_result_spec_string ("payto_uri",
+                                    &payto_uri),
+      GNUNET_PQ_result_spec_allow_null (
+        GNUNET_PQ_result_spec_string ("conversion_url",
+                                      &conversion_url),
+        NULL),
+      TALER_PQ_result_spec_json ("debit_restrictions",
+                                 &debit_restrictions),
+      TALER_PQ_result_spec_json ("credit_restrictions",
+                                 &credit_restrictions),
+      GNUNET_PQ_result_spec_end
+    };
+
+    if (GNUNET_OK !=
+        GNUNET_PQ_extract_result (result,
+                                  rs,
+                                  i))
+    {
+      GNUNET_break (0);
+      ctx->failed = true;
+      return;
+    }
+    ctx->cb (ctx->cb_cls,
+             payto_uri,
+             conversion_url,
+             debit_restrictions,
+             credit_restrictions,
+             &master_sig);
+    GNUNET_PQ_cleanup_result (rs);
+  }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_accounts_by_exchange (
+  void *cls,
+  const struct TALER_MasterPublicKeyP *master_pub,
+  TALER_MERCHANTDB_ExchangeAccountCallback cb,
+  void *cb_cls)
+{
+  struct PostgresClosure *pg = cls;
+  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_exchange_accounts",
+           "SELECT"
+           " 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_accounts_by_exchange.h 
b/src/backenddb/pg_select_accounts_by_exchange.h
new file mode 100644
index 00000000..e22c1601
--- /dev/null
+++ b/src/backenddb/pg_select_accounts_by_exchange.h
@@ -0,0 +1,45 @@
+/*
+   This file is part of TALER
+   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
+   Foundation; either version 3, or (at your option) any later version.
+
+   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR
+   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along with
+   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file backenddb/pg_select_accounts_by_exchange.h
+ * @brief implementation of the select_accounts_by_exchange function for 
Postgres
+ * @author Christian Grothoff
+ */
+#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>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * 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
+TMH_PG_select_accounts_by_exchange (
+  void *cls,
+  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..baaad568 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);
   }
@@ -9064,7 +9065,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 +9124,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 +9805,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/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_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_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",

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