gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] branch master updated (00658a9 -> 0b945df)


From: gnunet
Subject: [taler-merchant] branch master updated (00658a9 -> 0b945df)
Date: Fri, 10 Apr 2020 20:15:00 +0200

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

grothoff pushed a change to branch master
in repository merchant.

    from 00658a9  make code compile without warnings against latest 
libmicrohttpd API
     new 23ff0cc  fix twister check
     new 831272d  fix ftbfs
     new 0b945df  implement #5299

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


Summary of changes:
 configure.ac                                       |  14 +-
 src/backend/taler-merchant-httpd.c                 |   1 +
 src/backend/taler-merchant-httpd_check-payment.c   |   4 +-
 src/backend/taler-merchant-httpd_order.c           |   8 +-
 src/backend/taler-merchant-httpd_pay.c             |  35 +-
 src/backend/taler-merchant-httpd_poll-payment.c    |   4 +-
 src/backend/taler-merchant-httpd_refund.c          |   5 +-
 src/backend/taler-merchant-httpd_refund.h          |   8 +
 src/backend/taler-merchant-httpd_refund_lookup.c   | 619 ++++++++++++++++++---
 .../taler-merchant-httpd_tip-reserve-helper.c      |  12 +-
 .../taler-merchant-httpd_track-transaction.c       |  18 +-
 src/backend/taler-merchant-httpd_track-transfer.c  |  12 +-
 src/backenddb/merchant-0001.sql                    |  38 +-
 src/backenddb/plugin_merchantdb_postgres.c         |  75 ++-
 src/backenddb/test_merchantdb.c                    |   4 +-
 src/include/taler_merchant_service.h               |  57 +-
 src/include/taler_merchantdb_plugin.h              |   4 +-
 src/lib/merchant_api_common.c                      |  14 +-
 src/lib/merchant_api_pay.c                         |   2 +-
 src/lib/merchant_api_refund.c                      | 195 ++++++-
 src/lib/test_merchant_api.c                        |  84 ++-
 src/lib/test_merchant_api_twisted.c                | 580 ++++++++-----------
 src/lib/testing_api_cmd_check_payment.c            |   1 +
 src/lib/testing_api_cmd_history.c                  |  24 +-
 src/lib/testing_api_cmd_pay_abort_refund.c         |   4 +-
 src/lib/testing_api_cmd_poll_payment.c             |   1 +
 src/lib/testing_api_cmd_proposal.c                 |   3 +-
 src/lib/testing_api_cmd_refund_increase.c          |   7 +
 src/lib/testing_api_cmd_refund_lookup.c            | 302 +++++-----
 src/lib/testing_api_cmd_track_transaction.c        |  15 +-
 30 files changed, 1433 insertions(+), 717 deletions(-)

diff --git a/configure.ac b/configure.ac
index 742da9d..bbd95f3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -205,7 +205,7 @@ AC_CHECK_HEADERS([taler/taler_fakebank_lib.h],
 AM_CONDITIONAL(HAVE_TALERFAKEBANK, test x$libtalerfakebank = x1)
 
 
-# check for libtalertwistertesting
+# check for libtalertwister
 twistertesting=0
 AC_MSG_CHECKING([for talerwtistertesting])
 AC_ARG_WITH([twister],
@@ -218,14 +218,16 @@ AS_CASE([$with_twister],
         [no], [AC_MSG_WARN([no twister-testing will be compiled])],
         [LDFLAGS="-L$with_twister/lib $LDFLAGS"
          CPPFLAGS="-I$with_twister/include $CPPFLAGS"])
+AC_CHECK_LIB(talertwistertesting,TALER_TWISTER_run_twister,
+  [AC_CHECK_HEADER([taler/taler_twister_testing_lib.h],[twistertesting=1],,
+                   [#ifdef HAVE_GNUNET_PLATFORM_H
+                      #include <gnunet/platform.h>
+                    #endif
+                    ])])
 
-AC_CHECK_HEADERS([taler/taler_twister_testing_lib.h],
- [AC_CHECK_LIB([talertwistertesting], [TALER_TESTING_run_twister], 
twistertesting=1,, [-ltalerexchange -ltalerbank])],
-   [], [#ifdef HAVE_GNUNET_PLATFORM_H
-        #include <gnunet/platform.h>
-       #endif])
 AM_CONDITIONAL(HAVE_TWISTER, test x$twistertesting = x1)
 
+
 # gcov compilation
 AC_MSG_CHECKING(whether to compile with support for code coverage analysis)
 AC_ARG_ENABLE([coverage],
diff --git a/src/backend/taler-merchant-httpd.c 
b/src/backend/taler-merchant-httpd.c
index 7e8f70f..f577c47 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -406,6 +406,7 @@ do_shutdown (void *cls)
   (void) cls;
   MH_force_pc_resume ();
   MH_force_trh_resume ();
+  MH_force_refund_resume ();
   if (NULL != mhd_task)
   {
     GNUNET_SCHEDULER_cancel (mhd_task);
diff --git a/src/backend/taler-merchant-httpd_check-payment.c 
b/src/backend/taler-merchant-httpd_check-payment.c
index a72a6e3..66b4941 100644
--- a/src/backend/taler-merchant-httpd_check-payment.c
+++ b/src/backend/taler-merchant-httpd_check-payment.c
@@ -140,14 +140,16 @@ cprc_cleanup (struct TM_HandlerContext *hc)
  *
  * @param cls closure
  * @param coin_pub public coin from which the refund comes from
+ * @param exchange_url URL of the exchange that issued @a coin_pub
  * @param rtransaction_id identificator of the refund
  * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from coin_pub
+ * @param refund_amount refund amount which is being taken from @a coin_pub
  * @param refund_fee cost of this refund operation
  */
 static void
 process_refunds_cb (void *cls,
                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    const char *exchange_url,
                     uint64_t rtransaction_id,
                     const char *reason,
                     const struct TALER_Amount *refund_amount,
diff --git a/src/backend/taler-merchant-httpd_order.c 
b/src/backend/taler-merchant-httpd_order.c
index 374274e..1cf3463 100644
--- a/src/backend/taler-merchant-httpd_order.c
+++ b/src/backend/taler-merchant-httpd_order.c
@@ -488,12 +488,14 @@ proposal_put (struct MHD_Connection *connection,
       refund_deadline.abs_value_us)
   {
     GNUNET_JSON_parse_free (spec);
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "invariant failed: wire_transfer_deadline >= 
refund_deadline\n");
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "wire_transfer_deadline: %s\n",
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "wire_transfer_deadline: %s\n",
                 GNUNET_STRINGS_absolute_time_to_string (
                   wire_transfer_deadline));
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "refund_deadline: %s\n",
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "refund_deadline: %s\n",
                 GNUNET_STRINGS_absolute_time_to_string (refund_deadline));
     return TALER_MHD_reply_with_error
              (connection,
diff --git a/src/backend/taler-merchant-httpd_pay.c 
b/src/backend/taler-merchant-httpd_pay.c
index de65102..2d3bfcd 100644
--- a/src/backend/taler-merchant-httpd_pay.c
+++ b/src/backend/taler-merchant-httpd_pay.c
@@ -934,7 +934,7 @@ check_payment_sufficient (struct PayContext *pc)
     {
       GNUNET_break_op (0);
       resume_pay_with_error (pc,
-                             MHD_HTTP_BAD_REQUEST,
+                             MHD_HTTP_NOT_ACCEPTABLE,
                              TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES,
                              "contract not paid up due to fees (client may 
have calculated them badly)");
     }
@@ -942,7 +942,7 @@ check_payment_sufficient (struct PayContext *pc)
     {
       GNUNET_break_op (0);
       resume_pay_with_error (pc,
-                             MHD_HTTP_BAD_REQUEST,
+                             MHD_HTTP_NOT_ACCEPTABLE,
                              TALER_EC_PAY_PAYMENT_INSUFFICIENT,
                              "payment insufficient");
 
@@ -1021,9 +1021,9 @@ deposit_cb (void *cls,
                                   "exchange had an internal server error",
                                   "code",
                                   (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                  "exchange-code",
+                                  "exchange_code",
                                   (json_int_t) hr->ec,
-                                  "exchange-http-status",
+                                  "exchange_http_status",
                                   (json_int_t) hr->http_status));
     }
     else if (NULL == hr->reply)
@@ -1037,9 +1037,9 @@ deposit_cb (void *cls,
                                   "exchange failed, response body not even in 
JSON",
                                   "code",
                                   (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                  "exchange-code",
+                                  "exchange_code",
                                   (json_int_t) hr->ec,
-                                  "exchange-http-status",
+                                  "exchange_http_status",
                                   (json_int_t) hr->http_status));
     }
     else
@@ -1055,13 +1055,13 @@ deposit_cb (void *cls,
                                     "exchange failed on deposit of a coin",
                                     "code",
                                     (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                    "exchange-code",
+                                    "exchange_code",
                                     (json_int_t) hr->ec,
-                                    "exchange-http-status",
+                                    "exchange_http_status",
                                     (json_int_t) hr->http_status,
                                     "coin_pub",
                                     GNUNET_JSON_from_data_auto (&dc->coin_pub),
-                                    "exchange-reply",
+                                    "exchange_reply",
                                     hr->reply));
       else
         resume_pay_with_response (
@@ -1072,13 +1072,13 @@ deposit_cb (void *cls,
                                     "exchange failed on deposit of a coin",
                                     "code",
                                     (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                    "exchange-code",
+                                    "exchange_code",
                                     (json_int_t) hr->ec,
-                                    "exchange-http-status",
+                                    "exchange_http_status",
                                     (json_int_t) hr->http_status,
                                     "coin_pub",
                                     GNUNET_JSON_from_data_auto (&dc->coin_pub),
-                                    "exchange-reply",
+                                    "exchange_reply",
                                     hr->reply));
     }
     return;
@@ -1172,11 +1172,11 @@ process_pay_with_exchange (void *cls,
         "failed to obtain meta-data from exchange",
         "code",
         (json_int_t) TALER_EC_PAY_EXCHANGE_KEYS_FAILURE,
-        "exchange-http-status",
+        "exchange_http_status",
         (json_int_t) http_status,
-        "exchange-code",
+        "exchange_code",
         (json_int_t) ec,
-        "exchange-reply",
+        "exchange_reply",
         error_reply));
     return;
   }
@@ -1789,14 +1789,16 @@ parse_pay (struct MHD_Connection *connection,
  *
  * @param cls closure with a `struct PayContext`
  * @param coin_pub public coin from which the refund comes from
+ * @param exchange_url URL of the exchange that issued @a coin_pub
  * @param rtransaction_id identificator of the refund
  * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from coin_pub
+ * @param refund_amount refund amount which is being taken from @a coin_pub
  * @param refund_fee cost of this refund operation
  */
 static void
 check_coin_refunded (void *cls,
                      const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                     const char *exchange_url,
                      uint64_t rtransaction_id,
                      const char *reason,
                      const struct TALER_Amount *refund_amount,
@@ -1804,6 +1806,7 @@ check_coin_refunded (void *cls,
 {
   struct PayContext *pc = cls;
 
+  (void) exchange_url;
   for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
diff --git a/src/backend/taler-merchant-httpd_poll-payment.c 
b/src/backend/taler-merchant-httpd_poll-payment.c
index 281730f..8db7289 100644
--- a/src/backend/taler-merchant-httpd_poll-payment.c
+++ b/src/backend/taler-merchant-httpd_poll-payment.c
@@ -140,14 +140,16 @@ pprc_cleanup (struct TM_HandlerContext *hc)
  *
  * @param cls closure
  * @param coin_pub public coin from which the refund comes from
+ * @param exchange_url URL of the exchange that issued @a coin_pub
  * @param rtransaction_id identificator of the refund
  * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from coin_pub
+ * @param refund_amount refund amount which is being taken from @a coin_pub
  * @param refund_fee cost of this refund operation
  */
 static void
 process_refunds_cb (void *cls,
                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    const char *exchange_url,
                     uint64_t rtransaction_id,
                     const char *reason,
                     const struct TALER_Amount *refund_amount,
diff --git a/src/backend/taler-merchant-httpd_refund.c 
b/src/backend/taler-merchant-httpd_refund.c
index 79cb954..62ebf45 100644
--- a/src/backend/taler-merchant-httpd_refund.c
+++ b/src/backend/taler-merchant-httpd_refund.c
@@ -66,14 +66,16 @@ struct ProcessRefundData
  *
  * @param cls closure
  * @param coin_pub public coin from which the refund comes from
+ * @param exchange_url URL of the exchange that issued @a coin_pub
  * @param rtransaction_id identificator of the refund
  * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from coin_pub
+ * @param refund_amount refund amount which is being taken from @a coin_pub
  * @param refund_fee cost of this refund operation
  */
 static void
 process_refunds_cb (void *cls,
                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    const char *exchange_url,
                     uint64_t rtransaction_id,
                     const char *reason,
                     const struct TALER_Amount *refund_amount,
@@ -83,6 +85,7 @@ process_refunds_cb (void *cls,
   struct GNUNET_CRYPTO_EddsaSignature sig;
   json_t *element;
 
+  (void) exchange_url;
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Found refund of %s for coin %s with reason `%s' in database\n",
               TALER_B2S (coin_pub),
diff --git a/src/backend/taler-merchant-httpd_refund.h 
b/src/backend/taler-merchant-httpd_refund.h
index 1270f80..f0fb44d 100644
--- a/src/backend/taler-merchant-httpd_refund.h
+++ b/src/backend/taler-merchant-httpd_refund.h
@@ -45,6 +45,14 @@ MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
                           size_t *upload_data_size,
                           struct MerchantInstance *mi);
 
+
+/**
+ * Force resuming all suspended refund lookups, needed during shutdown.
+ */
+void
+MH_force_refund_resume (void);
+
+
 /**
  * Get the JSON representation of a refund.
  *
diff --git a/src/backend/taler-merchant-httpd_refund_lookup.c 
b/src/backend/taler-merchant-httpd_refund_lookup.c
index 2cae271..e80241b 100644
--- a/src/backend/taler-merchant-httpd_refund_lookup.c
+++ b/src/backend/taler-merchant-httpd_refund_lookup.c
@@ -22,9 +22,402 @@
 #include <jansson.h>
 #include <taler/taler_signatures.h>
 #include <taler/taler_json_lib.h>
+#include <taler/taler_exchange_service.h>
 #include "taler-merchant-httpd.h"
+#include "taler-merchant-httpd_exchanges.h"
 #include "taler-merchant-httpd_refund.h"
 
+/**
+ * How often do we retry DB transactions on serialization failures?
+ */
+#define MAX_RETRIES 5
+
+/**
+ * Information we keep for each coin to be refunded.
+ */
+struct CoinRefund
+{
+
+  /**
+   * Kept in a DLL.
+   */
+  struct CoinRefund *next;
+
+  /**
+   * Kept in a DLL.
+   */
+  struct CoinRefund *prev;
+
+  /**
+   * Request to connect to the target exchange.
+   */
+  struct TMH_EXCHANGES_FindOperation *fo;
+
+  /**
+   * Handle for the refund operation with the exchange.
+   */
+  struct TALER_EXCHANGE_RefundHandle *rh;
+
+  /**
+   * PRD this operation is part of.
+   */
+  struct ProcessRefundData *prd;
+
+  /**
+   * Coin to refund.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Refund transaction ID to use.
+   */
+  uint64_t rtransaction_id;
+
+  /**
+   * Amount to refund.
+   */
+  struct TALER_Amount refund_amount;
+
+  /**
+   * Applicable refund transaction fee.
+   */
+  struct TALER_Amount refund_fee;
+
+  /**
+   * Public key of the exchange affirming the refund.
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * Signature of the exchange affirming the refund.
+   */
+  struct TALER_ExchangeSignatureP exchange_sig;
+
+  /**
+   * HTTP status from the exchange, #MHD_HTTP_OK if
+   * @a exchange_pub and @a exchange_sig are valid.
+   */
+  unsigned int exchange_status;
+
+  /**
+   * HTTP error code from the exchange.
+   */
+  enum TALER_ErrorCode exchange_code;
+
+  /**
+   * Fully reply from the exchange, only possibly set if
+   * we got a JSON reply and a non-#MHD_HTTP_OK error code
+   */
+  json_t *exchange_reply;
+
+};
+
+
+/**
+ * Closure for #process_refunds_cb.
+ */
+struct ProcessRefundData
+{
+  /**
+   * Must be first for #handle_mhd_completion_callback() cleanup
+   * logic to work.
+   */
+  struct TM_HandlerContext hc;
+
+  /**
+   * Hashed version of contract terms.
+   */
+  struct GNUNET_HashCode h_contract_terms;
+
+  /**
+   * DLL of (suspended) requests.
+   */
+  struct ProcessRefundData *next;
+
+  /**
+   * DLL of (suspended) requests.
+   */
+  struct ProcessRefundData *prev;
+
+  /**
+   * Head of DLL of coin refunds for this request.
+   */
+  struct CoinRefund *cr_head;
+
+  /**
+   * Tail of DLL of coin refunds for this request.
+   */
+  struct CoinRefund *cr_tail;
+
+  /**
+   * Both public and private key are needed by the callback
+   */
+  const struct MerchantInstance *merchant;
+
+  /**
+   * Connection we are handling.
+   */
+  struct MHD_Connection *connection;
+
+  /**
+   * Did we suspend @a connection?
+   */
+  int suspended;
+
+  /**
+   * Return code: #TALER_EC_NONE if successful.
+   */
+  enum TALER_ErrorCode ec;
+};
+
+
+/**
+ * HEad of DLL of (suspended) requests.
+ */
+static struct ProcessRefundData *prd_head;
+
+/**
+ * Tail of DLL of (suspended) requests.
+ */
+static struct ProcessRefundData *prd_tail;
+
+
+/**
+ * Clean up memory in @a cls, the connection was closed.
+ *
+ * @param cls a `struct ProcessRefundData` to clean up.
+ */
+static void
+cleanup_prd (struct TM_HandlerContext *cls)
+{
+  struct ProcessRefundData *prd = (struct ProcessRefundData *) cls;
+  struct CoinRefund *cr;
+
+  while (NULL != (cr = prd->cr_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (prd->cr_head,
+                                 prd->cr_tail,
+                                 cr);
+    if (NULL != cr->fo)
+    {
+      TMH_EXCHANGES_find_exchange_cancel (cr->fo);
+      cr->fo = NULL;
+    }
+    if (NULL != cr->rh)
+    {
+      TALER_EXCHANGE_refund_cancel (cr->rh);
+      cr->rh = NULL;
+    }
+    if (NULL != cr->exchange_reply)
+    {
+      json_decref (cr->exchange_reply);
+      cr->exchange_reply = NULL;
+    }
+    GNUNET_free (cr);
+  }
+  GNUNET_free (prd);
+}
+
+
+/**
+ * Check if @a prd has sub-activities still pending.
+ *
+ * @param prd request to check
+ * @return #GNUNET_YES if activities are still pending
+ */
+static int
+prd_pending (struct ProcessRefundData *prd)
+{
+  int pending = GNUNET_NO;
+
+  for (struct CoinRefund *cr = prd->cr_head;
+       NULL != cr;
+       cr = cr->next)
+  {
+    if ( (NULL != cr->fo) ||
+         (NULL != cr->rh) )
+    {
+      pending = GNUNET_YES;
+      break;
+    }
+  }
+  return pending;
+}
+
+
+/**
+ * Check if @a prd is ready to be resumed, and if so, do it.
+ *
+ * @param prd refund request to be possibly ready
+ */
+static void
+check_resume_prd (struct ProcessRefundData *prd)
+{
+  if (prd_pending (prd))
+    return;
+  GNUNET_CONTAINER_DLL_remove (prd_head,
+                               prd_tail,
+                               prd);
+  GNUNET_assert (prd->suspended);
+  prd->suspended = GNUNET_NO;
+  MHD_resume_connection (prd->connection);
+  TMH_trigger_daemon ();
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * 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
+ */
+static void
+refund_cb (void *cls,
+           const struct TALER_EXCHANGE_HttpResponse *hr,
+           const struct TALER_ExchangePublicKeyP *exchange_pub,
+           const struct TALER_ExchangeSignatureP *exchange_sig)
+{
+  struct CoinRefund *cr = cls;
+
+  cr->rh = NULL;
+  cr->exchange_status = hr->http_status;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Exchange refund status for coin %s is %u\n",
+              TALER_B2S (&cr->coin_pub),
+              hr->http_status);
+  if (MHD_HTTP_OK != hr->http_status)
+  {
+    cr->exchange_code = hr->ec;
+    cr->exchange_reply = json_incref ((json_t*) hr->reply);
+  }
+  else
+  {
+    cr->exchange_pub = *exchange_pub;
+    cr->exchange_sig = *exchange_sig;
+    /* FIXME: store in our database,
+       1) as evidence for us that the refund happened, and
+       2) to possibly avoid doing another exchange iteration
+       the next time around. */
+  }
+  check_resume_prd (cr->prd);
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_find_exchange()
+ * operation.
+ *
+ * @param cls a `struct CoinRefund *`
+ * @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 #GNUNET_YES if this exchange is trusted by config
+ * @param ec error code, #TALER_EC_NONE on success
+ * @param http_status the HTTP status we got from the exchange
+ * @param error_reply the full reply from the exchange, NULL if
+ *        the response was NOT in JSON or on success
+ */
+static void
+exchange_found_cb (void *cls,
+                   struct TALER_EXCHANGE_Handle *eh,
+                   const struct TALER_Amount *wire_fee,
+                   int exchange_trusted,
+                   enum TALER_ErrorCode ec,
+                   unsigned int http_status,
+                   const json_t *error_reply)
+{
+  struct CoinRefund *cr = cls;
+
+  cr->fo = NULL;
+  if (TALER_EC_NONE == ec)
+  {
+    cr->rh = TALER_EXCHANGE_refund (eh,
+                                    &cr->refund_amount,
+                                    &cr->refund_fee,
+                                    &cr->prd->h_contract_terms,
+                                    &cr->coin_pub,
+                                    cr->rtransaction_id,
+                                    &cr->prd->merchant->privkey,
+                                    &refund_cb,
+                                    cr);
+    return;
+  }
+  cr->exchange_status = http_status;
+  cr->exchange_code = ec;
+  cr->exchange_reply = json_incref ((json_t*) error_reply);
+  check_resume_prd (cr->prd);
+}
+
+
+/**
+ * Function called with information about a refund.
+ * It is responsible for packing up the data to return.
+ *
+ * @param cls closure
+ * @param coin_pub public coin from which the refund comes from
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param rtransaction_id identificator of the refund
+ * @param reason human-readable explanation of the refund
+ * @param refund_amount refund amount which is being taken from @a coin_pub
+ * @param refund_fee cost of this refund operation
+ */
+static void
+process_refunds_cb (void *cls,
+                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    const char *exchange_url,
+                    uint64_t rtransaction_id,
+                    const char *reason,
+                    const struct TALER_Amount *refund_amount,
+                    const struct TALER_Amount *refund_fee)
+{
+  struct ProcessRefundData *prd = cls;
+  struct CoinRefund *cr;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Found refund of %s for coin %s with reason `%s' in database\n",
+              TALER_B2S (coin_pub),
+              TALER_amount2s (refund_amount),
+              reason);
+  cr = GNUNET_new (struct CoinRefund);
+  cr->prd = prd;
+  cr->coin_pub = *coin_pub;
+  cr->rtransaction_id = rtransaction_id;
+  cr->refund_amount = *refund_amount;
+  cr->refund_fee = *refund_fee;
+  GNUNET_CONTAINER_DLL_insert (prd->cr_head,
+                               prd->cr_tail,
+                               cr);
+  /* FIXME: check in database if we already got the
+     results from #refund_cb() from an earlier request,
+     if so, avoid this step: */
+  cr->fo = TMH_EXCHANGES_find_exchange (exchange_url,
+                                        NULL,
+                                        &exchange_found_cb,
+                                        cr);
+}
+
+
+/**
+ * Force resuming all suspended refund lookups, needed during shutdown.
+ */
+void
+MH_force_refund_resume (void)
+{
+  struct ProcessRefundData *prd;
+
+  while (NULL != (prd = prd_head))
+  {
+    GNUNET_CONTAINER_DLL_remove (prd_head,
+                                 prd_tail,
+                                 prd);
+    GNUNET_assert (prd->suspended);
+    prd->suspended = GNUNET_NO;
+    MHD_resume_connection (prd->connection);
+  }
+}
+
 
 /**
  * Return refund situation about a contract.
@@ -45,91 +438,181 @@ MH_handler_refund_lookup (struct TMH_RequestHandler *rh,
                           size_t *upload_data_size,
                           struct MerchantInstance *mi)
 {
+  struct ProcessRefundData *prd;
   const char *order_id;
-  struct GNUNET_HashCode h_contract_terms;
   json_t *contract_terms;
   enum GNUNET_DB_QueryStatus qs;
 
-  order_id = MHD_lookup_connection_value (connection,
-                                          MHD_GET_ARGUMENT_KIND,
-                                          "order_id");
-  if (NULL == order_id)
+  prd = *connection_cls;
+  if (NULL == prd)
   {
-    GNUNET_break_op (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PARAMETER_MISSING,
-                                       "order_id");
+    order_id = MHD_lookup_connection_value (connection,
+                                            MHD_GET_ARGUMENT_KIND,
+                                            "order_id");
+    if (NULL == order_id)
+    {
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_PARAMETER_MISSING,
+                                         "order_id");
+    }
+
+    /* Convert order id to h_contract_terms */
+    contract_terms = NULL;
+    db->preflight (db->cls);
+    qs = db->find_contract_terms (db->cls,
+                                  &contract_terms,
+                                  order_id,
+                                  &mi->pubkey);
+    if (0 > qs)
+    {
+      /* single, read-only SQL statements should never cause
+         serialization problems */
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_REFUND_LOOKUP_DB_ERROR,
+                                         "database error looking up order_id 
from merchant_contract_terms table");
+    }
+
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Unknown order id given: `%s'\n",
+                  order_id);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         TALER_EC_REFUND_ORDER_ID_UNKNOWN,
+                                         "order_id not found in database");
+    }
+
+    prd = GNUNET_new (struct ProcessRefundData);
+    if (GNUNET_OK !=
+        TALER_JSON_hash (contract_terms,
+                         &prd->h_contract_terms))
+    {
+      GNUNET_break (0);
+      json_decref (contract_terms);
+      GNUNET_free (prd);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_INTERNAL_LOGIC_ERROR,
+                                         "Could not hash contract terms");
+    }
+    json_decref (contract_terms);
+    prd->hc.cc = &cleanup_prd;
+    prd->merchant = mi;
+    prd->ec = TALER_EC_NONE;
+    prd->connection = connection;
+    *connection_cls = prd;
+
+    for (unsigned int i = 0; i<MAX_RETRIES; i++)
+    {
+      qs = db->get_refunds_from_contract_terms_hash (db->cls,
+                                                     &mi->pubkey,
+                                                     &prd->h_contract_terms,
+                                                     &process_refunds_cb,
+                                                     prd);
+      if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+        break;
+    }
+    if (0 > qs)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Database hard error on refunds_from_contract_terms_hash 
lookup: %s\n",
+                  GNUNET_h2s (&prd->h_contract_terms));
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_REFUND_LOOKUP_DB_ERROR,
+                                         "Failed to lookup refunds for 
contract");
+    }
   }
 
-  /* Convert order id to h_contract_terms */
-  contract_terms = NULL;
-  db->preflight (db->cls);
-  qs = db->find_contract_terms (db->cls,
-                                &contract_terms,
-                                order_id,
-                                &mi->pubkey);
-  if (0 > qs)
+  /* Check if there are still exchange operations pending */
+  if (GNUNET_YES == prd_pending (prd))
   {
-    /* single, read-only SQL statements should never cause
-       serialization problems */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error as well to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_REFUND_LOOKUP_DB_ERROR,
-                                       "database error looking up order_id 
from merchant_contract_terms table");
+    if (! prd->suspended)
+    {
+      prd->suspended = GNUNET_YES;
+      MHD_suspend_connection (connection);
+      GNUNET_CONTAINER_DLL_insert (prd_head,
+                                   prd_tail,
+                                   prd);
+    }
+    return MHD_YES;   /* we're still talking to the exchange */
   }
 
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  /* All operations done, build final response */
+  if (NULL == prd->cr_head)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Unknown order id given: `%s'\n",
-                order_id);
+    /* There ARE no refunds scheduled, bitch */
     return TALER_MHD_reply_with_error (connection,
                                        MHD_HTTP_NOT_FOUND,
-                                       TALER_EC_REFUND_ORDER_ID_UNKNOWN,
-                                       "order_id not found in database");
+                                       TALER_EC_REFUND_LOOKUP_NO_REFUND,
+                                       "This contract is not currently 
eligible for refunds");
   }
 
-  if (GNUNET_OK !=
-      TALER_JSON_hash (contract_terms,
-                       &h_contract_terms))
   {
-    GNUNET_break (0);
-    json_decref (contract_terms);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_INTERNAL_LOGIC_ERROR,
-                                       "Could not hash contract terms");
-  }
-  json_decref (contract_terms);
+    json_t *ra;
 
-  {
-    json_t *response;
-    enum TALER_ErrorCode ec;
-    const char *errmsg;
-
-    response = TM_get_refund_json (mi,
-                                   &h_contract_terms,
-                                   &ec,
-                                   &errmsg);
-    if (NULL == response)
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         ec,
-                                         errmsg);
-    return TALER_MHD_reply_json_pack (connection, MHD_HTTP_OK,
-                                      "{s:o, s:o, s:o}",
-                                      "refund_permissions",
-                                      response,
-                                      "merchant_pub",
-                                      GNUNET_JSON_from_data_auto (
-                                        &mi->pubkey),
-                                      "h_contract_terms",
-                                      GNUNET_JSON_from_data_auto (
-                                        &h_contract_terms));
+    ra = json_array ();
+    GNUNET_assert (NULL != ra);
+    for (struct CoinRefund *cr = prd->cr_head;
+         NULL != cr;
+         cr = cr->next)
+    {
+      GNUNET_assert (
+        0 ==
+        json_array_append_new (
+          ra,
+          (MHD_HTTP_OK != cr->exchange_status)
+          ? json_pack ((NULL != cr->exchange_reply)
+                       ? "{s:o,s:o,s:o,s:I,s:I,s:I,s:O}"
+                       : "{s:o,s:o,s:o,s:I,s:I:s:I}",
+                       "coin_pub",
+                       GNUNET_JSON_from_data_auto (&cr->coin_pub),
+                       "refund_amount",
+                       TALER_JSON_from_amount (&cr->refund_amount),
+                       "refund_fee",
+                       TALER_JSON_from_amount (&cr->refund_fee),
+                       "exchange_http_status",
+                       (json_int_t) cr->exchange_status,
+                       "rtransaction_id",
+                       (json_int_t) cr->rtransaction_id,
+                       "exchange_code",
+                       (json_int_t) cr->exchange_code,
+                       "exchange_reply",
+                       cr->exchange_reply)
+          : json_pack ("{s:o,s:o,s:o,s:I,s:I,s:o,s:o}",
+                       "coin_pub",
+                       GNUNET_JSON_from_data_auto (&cr->coin_pub),
+                       "refund_amount",
+                       TALER_JSON_from_amount (&cr->refund_amount),
+                       "refund_fee",
+                       TALER_JSON_from_amount (&cr->refund_fee),
+                       "exchange_http_status",
+                       (json_int_t) cr->exchange_status,
+                       "rtransaction_id",
+                       (json_int_t) cr->rtransaction_id,
+                       "exchange_pub",
+                       GNUNET_JSON_from_data_auto (&cr->exchange_pub),
+                       "exchange_sig",
+                       GNUNET_JSON_from_data_auto (&cr->exchange_sig)
+                       )));
+    }
+    return TALER_MHD_reply_json_pack (
+      connection,
+      MHD_HTTP_OK,
+      "{s:o, s:o, s:o}",
+      "refunds",
+      ra,
+      "merchant_pub",
+      GNUNET_JSON_from_data_auto (&mi->pubkey),
+      "h_contract_terms",
+      GNUNET_JSON_from_data_auto (&prd->h_contract_terms));
   }
 }
 
diff --git a/src/backend/taler-merchant-httpd_tip-reserve-helper.c 
b/src/backend/taler-merchant-httpd_tip-reserve-helper.c
index 190aba2..fc5a1aa 100644
--- a/src/backend/taler-merchant-httpd_tip-reserve-helper.c
+++ b/src/backend/taler-merchant-httpd_tip-reserve-helper.c
@@ -101,10 +101,10 @@ handle_status (void *cls,
       TALER_MHD_make_json_pack (
         "{s:I, s:I, s:s, s:I, s:O}",
         "code", (json_int_t) TALER_EC_TIP_QUERY_RESERVE_UNKNOWN_TO_EXCHANGE,
-        "exchange-http-status", hr->http_status,
+        "exchange_http_status", hr->http_status,
         "hint", "tipping reserve unknown at exchange",
-        "exchange-code", hr->ec,
-        "exchange-reply", hr->reply));
+        "exchange_code", hr->ec,
+        "exchange_reply", hr->reply));
     return;
   }
   if (MHD_HTTP_OK != hr->http_status)
@@ -116,10 +116,10 @@ handle_status (void *cls,
       TALER_MHD_make_json_pack (
         "{s:I, s:I, s:s, s:I, s:O}",
         "code", (json_int_t) TALER_EC_TIP_QUERY_RESERVE_HISTORY_FAILED,
-        "exchange-http-status", hr->http_status,
+        "exchange_http_status", hr->http_status,
         "hint", "exchange failed to provide reserve history",
-        "exchange-code", (json_int_t) hr->ec,
-        "exchange-reply", hr->reply));
+        "exchange_code", (json_int_t) hr->ec,
+        "exchange_reply", hr->reply));
     return;
   }
 
diff --git a/src/backend/taler-merchant-httpd_track-transaction.c 
b/src/backend/taler-merchant-httpd_track-transaction.c
index 290e8dc..b64bf2e 100644
--- a/src/backend/taler-merchant-httpd_track-transaction.c
+++ b/src/backend/taler-merchant-httpd_track-transaction.c
@@ -471,11 +471,11 @@ wire_deposits_cb (void *cls,
         "{s:I, s:I, s:I, s:O}",
         "code",
         (json_int_t) TALER_EC_TRACK_TRANSACTION_WIRE_TRANSFER_TRACE_ERROR,
-        "exchange-http-status",
+        "exchange_http_status",
         (json_int_t) hr->http_status,
-        "exchange-code",
+        "exchange_code",
         (json_int_t) hr->ec,
-        "exchange-reply",
+        "exchange_reply",
         hr->reply));
     return;
   }
@@ -629,11 +629,11 @@ wtid_cb (void *cls,
         "{s:I, s:I, s:I, s:O}",
         "code",
         (json_int_t) TALER_EC_TRACK_TRANSACTION_COIN_TRACE_ERROR,
-        "exchange-http-status",
+        "exchange_http_status",
         (json_int_t) hr->http_status,
-        "exchange-code",
+        "exchange_code",
         (json_int_t) hr->ec,
-        "exchange-reply",
+        "exchange_reply",
         hr->reply));
     return;
   }
@@ -905,9 +905,9 @@ process_track_transaction_with_exchange (void *cls,
         : "{s:s, s:I, s:I, s:I}",
         "hint", "failed to obtain meta-data from exchange",
         "code", (json_int_t) TALER_EC_TRACK_TRANSACTION_EXCHANGE_KEYS_FAILURE,
-        "exchange-http-status", (json_int_t) http_status,
-        "exchange-code", (json_int_t) ec,
-        "exchange-reply", error_reply));
+        "exchange_http_status", (json_int_t) http_status,
+        "exchange_code", (json_int_t) ec,
+        "exchange_reply", error_reply));
     return;
   }
   tctx->eh = eh;
diff --git a/src/backend/taler-merchant-httpd_track-transfer.c 
b/src/backend/taler-merchant-httpd_track-transfer.c
index 2ddcc16..000738b 100644
--- a/src/backend/taler-merchant-httpd_track-transfer.c
+++ b/src/backend/taler-merchant-httpd_track-transfer.c
@@ -617,9 +617,9 @@ wire_transfer_cb (void *cls,
       TALER_MHD_make_json_pack (
         "{s:I, s:I, s:I, s:O}",
         "code", (json_int_t) TALER_EC_TRACK_TRANSFER_EXCHANGE_ERROR,
-        "exchange-code", (json_int_t) hr->ec,
-        "exchange-http-status", (json_int_t) hr->http_status,
-        "exchange-reply", hr->reply));
+        "exchange_code", (json_int_t) hr->ec,
+        "exchange_http_status", (json_int_t) hr->http_status,
+        "exchange_reply", hr->reply));
     return;
   }
   for (unsigned int i = 0; i<MAX_RETRIES; i++)
@@ -828,9 +828,9 @@ process_track_transfer_with_exchange (void *cls,
         : "{s:s, s:I, s:I, s:I}",
         "hint", "failed to obtain meta-data from exchange",
         "code", (json_int_t) TALER_EC_TRACK_TRANSFER_EXCHANGE_KEYS_FAILURE,
-        "exchange-http-status", (json_int_t) http_status,
-        "exchange-code", (json_int_t) ec,
-        "exchange-reply", error_reply));
+        "exchange_http_status", (json_int_t) http_status,
+        "exchange_code", (json_int_t) ec,
+        "exchange_reply", error_reply));
     return;
   }
   rctx->eh = eh;
diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql
index 98f20a2..bfcc828 100644
--- a/src/backenddb/merchant-0001.sql
+++ b/src/backenddb/merchant-0001.sql
@@ -21,7 +21,7 @@ BEGIN;
 SELECT _v.register_patch('merchant-0001', NULL, NULL);
 
 
-CREATE TABLE IF NOT EXISTS merchant_orders 
+CREATE TABLE IF NOT EXISTS merchant_orders
   (order_id VARCHAR NOT NULL
   ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
   ,contract_terms BYTEA NOT NULL
@@ -29,7 +29,7 @@ CREATE TABLE IF NOT EXISTS merchant_orders
   ,PRIMARY KEY (order_id, merchant_pub)
   );
 
--- Offers we made to customers 
+-- Offers we made to customers
 CREATE TABLE IF NOT EXISTS merchant_contract_terms
    (order_id VARCHAR NOT NULL
    ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
@@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS merchant_contract_terms
    ,UNIQUE (h_contract_terms, merchant_pub)
    );
 
--- Table with the proofs for each coin we deposited at the exchange 
+-- Table with the proofs for each coin we deposited at the exchange
 CREATE TABLE IF NOT EXISTS merchant_deposits
   (h_contract_terms BYTEA NOT NULL
   ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
@@ -62,7 +62,7 @@ CREATE TABLE IF NOT EXISTS merchant_deposits
   ,FOREIGN KEY (h_contract_terms, merchant_pub) REFERENCES 
merchant_contract_terms (h_contract_terms, merchant_pub)
   );
 
-CREATE TABLE IF NOT EXISTS merchant_proofs 
+CREATE TABLE IF NOT EXISTS merchant_proofs
   (exchange_url VARCHAR NOT NULL
   ,wtid BYTEA CHECK (LENGTH(wtid)=32)
   ,execution_time INT8 NOT NULL
@@ -70,10 +70,10 @@ CREATE TABLE IF NOT EXISTS merchant_proofs
   ,proof BYTEA NOT NULL
   ,PRIMARY KEY (wtid, exchange_url)
   );
-  
+
 -- Note that h_contract_terms + coin_pub may actually be unknown to
 -- us, e.g. someone else deposits something for us at the exchange.
--- Hence those cannot be foreign keys into deposits/transactions! 
+-- Hence those cannot be foreign keys into deposits/transactions!
 CREATE TABLE IF NOT EXISTS merchant_transfers
   (h_contract_terms BYTEA NOT NULL
   ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)
@@ -88,7 +88,7 @@ CREATE INDEX IF NOT EXISTS merchant_transfers_by_wtid
   ON merchant_transfers
   (wtid);
 
-CREATE TABLE IF NOT EXISTS exchange_wire_fees 
+CREATE TABLE IF NOT EXISTS exchange_wire_fees
   (exchange_pub BYTEA NOT NULL CHECK (LENGTH(exchange_pub)=32)
   ,h_wire_method BYTEA NOT NULL CHECK (LENGTH(h_wire_method)=64)
   ,wire_fee_val INT8 NOT NULL
@@ -100,20 +100,20 @@ CREATE TABLE IF NOT EXISTS exchange_wire_fees
   ,exchange_sig BYTEA NOT NULL CHECK (LENGTH(exchange_sig)=64)
   ,PRIMARY KEY (exchange_pub,h_wire_method,start_date,end_date)
   );
-  
+
 CREATE TABLE IF NOT EXISTS merchant_refunds
   (rtransaction_id BIGSERIAL UNIQUE
-  ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
+  ,merchant_pub BYTEA NOT NULL
   ,h_contract_terms BYTEA NOT NULL
-  ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)
+  ,coin_pub BYTEA NOT NULL
   ,reason VARCHAR NOT NULL
   ,refund_amount_val INT8 NOT NULL
   ,refund_amount_frac INT4 NOT NULL
-  ,refund_fee_val INT8 NOT NULL
-  ,refund_fee_frac INT4 NOT NULL
+  ,FOREIGN KEY (h_contract_terms, coin_pub) REFERENCES merchant_deposits 
(h_contract_terms, coin_pub)
+  ,FOREIGN KEY (h_contract_terms, merchant_pub) REFERENCES 
merchant_contract_terms (h_contract_terms, merchant_pub)
   );
 
--- balances of the reserves available for tips 
+-- balances of the reserves available for tips
 CREATE TABLE IF NOT EXISTS merchant_tip_reserves
   (reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)
   ,expiration INT8 NOT NULL
@@ -121,8 +121,8 @@ CREATE TABLE IF NOT EXISTS merchant_tip_reserves
   ,balance_frac INT4 NOT NULL
   ,PRIMARY KEY (reserve_priv)
   );
-  
--- table where we remember when tipping reserves where established / enabled 
+
+-- table where we remember when tipping reserves where established / enabled
 CREATE TABLE IF NOT EXISTS merchant_tip_reserve_credits
   (reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)
   ,credit_uuid BYTEA UNIQUE NOT NULL CHECK (LENGTH(credit_uuid)=64)
@@ -132,8 +132,8 @@ CREATE TABLE IF NOT EXISTS merchant_tip_reserve_credits
   ,PRIMARY KEY (credit_uuid)
   );
 
--- tips that have been authorized 
-CREATE TABLE IF NOT EXISTS merchant_tips 
+-- tips that have been authorized
+CREATE TABLE IF NOT EXISTS merchant_tips
   (reserve_priv BYTEA NOT NULL CHECK (LENGTH(reserve_priv)=32)
   ,tip_id BYTEA NOT NULL CHECK (LENGTH(tip_id)=64)
   ,exchange_url VARCHAR NOT NULL
@@ -148,7 +148,7 @@ CREATE TABLE IF NOT EXISTS merchant_tips
   );
 
 -- tips that have been picked up
-CREATE TABLE IF NOT EXISTS merchant_tip_pickups 
+CREATE TABLE IF NOT EXISTS merchant_tip_pickups
   (tip_id BYTEA NOT NULL REFERENCES merchant_tips (tip_id) ON DELETE CASCADE
   ,pickup_id BYTEA NOT NULL CHECK (LENGTH(pickup_id)=64)
   ,amount_val INT8 NOT NULL
@@ -156,7 +156,7 @@ CREATE TABLE IF NOT EXISTS merchant_tip_pickups
   ,PRIMARY KEY (pickup_id)
   );
 
--- sessions and their order_id/fulfillment_url mapping 
+-- sessions and their order_id/fulfillment_url mapping
 CREATE TABLE IF NOT EXISTS merchant_session_info
   (session_id VARCHAR NOT NULL
   ,fulfillment_url VARCHAR NOT NULL
diff --git a/src/backenddb/plugin_merchantdb_postgres.c 
b/src/backenddb/plugin_merchantdb_postgres.c
index 61de4a5..18b4f76 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -1742,9 +1742,12 @@ get_refunds_cb (void *cls,
     struct TALER_Amount refund_amount;
     struct TALER_Amount refund_fee;
     char *reason;
+    char *exchange_url;
     struct GNUNET_PQ_ResultSpec rs[] = {
       GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
                                             &coin_pub),
+      GNUNET_PQ_result_spec_string ("exchange_url",
+                                    &exchange_url),
       GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
                                     &rtransaction_id),
       TALER_PQ_RESULT_SPEC_AMOUNT ("refund_amount",
@@ -1768,6 +1771,7 @@ get_refunds_cb (void *cls,
     grc->qs = i + 1;
     grc->rc (grc->rc_cls,
              &coin_pub,
+             exchange_url,
              rtransaction_id,
              reason,
              &refund_amount,
@@ -1788,15 +1792,12 @@ get_refunds_cb (void *cls,
  * @return transaction status
  */
 static enum GNUNET_DB_QueryStatus
-postgres_get_refunds_from_contract_terms_hash (void *cls,
-                                               const struct
-                                               TALER_MerchantPublicKeyP *
-                                               merchant_pub,
-                                               const struct
-                                               GNUNET_HashCode 
*h_contract_terms,
-                                               TALER_MERCHANTDB_RefundCallback
-                                               rc,
-                                               void *rc_cls)
+postgres_get_refunds_from_contract_terms_hash (
+  void *cls,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct GNUNET_HashCode *h_contract_terms,
+  TALER_MERCHANTDB_RefundCallback rc,
+  void *rc_cls)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
@@ -1837,7 +1838,6 @@ postgres_get_refunds_from_contract_terms_hash (void *cls,
  * @param coin_pub public key of the coin giving the (part of) refund
  * @param reason human readable explanation behind the refund
  * @param refund how much this coin is refunding
- * @param refund_fee refund fee for this coin
  */
 static enum GNUNET_DB_QueryStatus
 insert_refund (void *cls,
@@ -1845,8 +1845,7 @@ insert_refund (void *cls,
                const struct GNUNET_HashCode *h_contract_terms,
                const struct TALER_CoinSpendPublicKeyP *coin_pub,
                const char *reason,
-               const struct TALER_Amount *refund,
-               const struct TALER_Amount *refund_fee)
+               const struct TALER_Amount *refund)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
@@ -1855,7 +1854,6 @@ insert_refund (void *cls,
     GNUNET_PQ_query_param_auto_from_type (coin_pub),
     GNUNET_PQ_query_param_string (reason),
     TALER_PQ_query_param_amount (refund),
-    TALER_PQ_query_param_amount (refund_fee),
     GNUNET_PQ_query_param_end
   };
 
@@ -1886,17 +1884,18 @@ insert_refund (void *cls,
  * @return transaction status code
  */
 static enum GNUNET_DB_QueryStatus
-postgres_store_wire_fee_by_exchange (void *cls,
-                                     const struct
-                                     TALER_MasterPublicKeyP *exchange_pub,
-                                     const struct
-                                     GNUNET_HashCode *h_wire_method,
-                                     const struct TALER_Amount *wire_fee,
-                                     const struct TALER_Amount *closing_fee,
-                                     struct GNUNET_TIME_Absolute start_date,
-                                     struct GNUNET_TIME_Absolute end_date,
-                                     const struct
-                                     TALER_MasterSignatureP *exchange_sig)
+postgres_store_wire_fee_by_exchange (
+  void *cls,
+  const struct
+  TALER_MasterPublicKeyP *exchange_pub,
+  const struct
+  GNUNET_HashCode *h_wire_method,
+  const struct TALER_Amount *wire_fee,
+  const struct TALER_Amount *closing_fee,
+  struct GNUNET_TIME_Absolute start_date,
+  struct GNUNET_TIME_Absolute end_date,
+  const struct
+  TALER_MasterSignatureP *exchange_sig)
 {
   struct PostgresClosure *pg = cls;
   struct GNUNET_PQ_QueryParam params[] = {
@@ -2109,7 +2108,6 @@ process_deposits_for_refund_cb (void *cls,
   struct TALER_Amount deposit_refund[GNUNET_NZL (num_results)];
   struct TALER_CoinSpendPublicKeyP deposit_coin_pubs[GNUNET_NZL (num_results)];
   struct TALER_Amount deposit_amount_with_fee[GNUNET_NZL (num_results)];
-  struct TALER_Amount deposit_refund_fee[GNUNET_NZL (num_results)];
 
   GNUNET_assert (GNUNET_OK ==
                  TALER_amount_get_zero (ctx->refund->currency,
@@ -2121,14 +2119,11 @@ process_deposits_for_refund_cb (void *cls,
   {
     struct TALER_CoinSpendPublicKeyP coin_pub;
     struct TALER_Amount amount_with_fee;
-    struct TALER_Amount refund_fee;
     struct GNUNET_PQ_ResultSpec rs[] = {
       GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
                                             &coin_pub),
       TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
                                    &amount_with_fee),
-      TALER_PQ_RESULT_SPEC_AMOUNT ("refund_fee",
-                                   &refund_fee),
       GNUNET_PQ_result_spec_end
     };
     struct FindRefundContext ictx = {
@@ -2173,7 +2168,6 @@ process_deposits_for_refund_cb (void *cls,
     deposit_refund[i] = ictx.refunded_amount;
     deposit_amount_with_fee[i] = amount_with_fee;
     deposit_coin_pubs[i] = coin_pub;
-    deposit_refund_fee[i] = refund_fee;
     if (0 >
         TALER_amount_add (&current_refund,
                           &current_refund,
@@ -2283,8 +2277,7 @@ process_deposits_for_refund_cb (void *cls,
                                ctx->h_contract_terms,
                                &deposit_coin_pubs[i],
                                ctx->reason,
-                               increment,
-                               &deposit_refund_fee[i])))
+                               increment)))
       {
         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
         ctx->qs = qs;
@@ -3074,11 +3067,9 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
                             ",reason"
                             ",refund_amount_val"
                             ",refund_amount_frac"
-                            ",refund_fee_val"
-                            ",refund_fee_frac"
                             ") VALUES"
-                            "($1, $2, $3, $4, $5, $6, $7, $8)",
-                            8),
+                            "($1, $2, $3, $4, $5, $6)",
+                            6),
     GNUNET_PQ_make_prepare ("insert_proof",
                             "INSERT INTO merchant_proofs"
                             "(exchange_url"
@@ -3228,15 +3219,17 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
     GNUNET_PQ_make_prepare ("find_refunds_from_contract_terms_hash",
                             "SELECT"
                             " coin_pub"
+                            ",merchant_deposits.exchange_url"
                             ",rtransaction_id"
                             ",refund_amount_val"
                             ",refund_amount_frac"
-                            ",refund_fee_val"
-                            ",refund_fee_frac"
+                            ",merchant_deposits.refund_fee_val"
+                            ",merchant_deposits.refund_fee_frac"
                             ",reason"
                             " FROM merchant_refunds"
-                            " WHERE merchant_pub=$1"
-                            " AND h_contract_terms=$2",
+                            "   JOIN merchant_deposits USING (merchant_pub, 
coin_pub)"
+                            " WHERE merchant_refunds.merchant_pub=$1"
+                            "   AND merchant_refunds.h_contract_terms=$2",
                             2),
     GNUNET_PQ_make_prepare ("find_contract_terms_by_date_and_range_asc",
                             "SELECT"
@@ -3354,9 +3347,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
                             ",merchant_deposits.exchange_proof"
                             " FROM merchant_transfers"
                             "   JOIN merchant_deposits"
-                            "     ON (merchant_deposits.h_contract_terms = 
merchant_transfers.h_contract_terms"
-                            "       AND"
-                            "         merchant_deposits.coin_pub = 
merchant_transfers.coin_pub)"
+                            "     USING (h_contract_terms,coin_pub)"
                             " WHERE wtid=$1",
                             1),
     GNUNET_PQ_make_prepare ("find_proof_by_wtid",
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index 87b8c73..9e3dd22 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -199,14 +199,16 @@ static json_t *contract_terms_future;
  *
  * @param cls closure
  * @param coin_pub public coin from which the refund comes from
+ * @param exchange_url URL of the exchange that issued the @a coin_pub
  * @param rtransaction_id identificator of the refund
  * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from coin_pub
+ * @param refund_amount refund amount which is being taken from @a coin_pub
  * @param refund_fee cost of this refund operation
  */
 static void
 refund_cb (void *cls,
            const struct TALER_CoinSpendPublicKeyP *coin_pub,
+           const char *exchange_url,
            uint64_t rtransaction_id,
            const char *reason,
            const struct TALER_Amount *refund_amount,
diff --git a/src/include/taler_merchant_service.h 
b/src/include/taler_merchant_service.h
index ac7e427..ef616c1 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -298,16 +298,71 @@ TALER_MERCHANT_config_get_cancel (struct 
TALER_MERCHANT_ConfigGetHandle *vgh);
 struct TALER_MERCHANT_RefundLookupOperation;
 
 
+/**
+ * Detail about a refund lookup result.
+ */
+struct TALER_MERCHANT_RefundDetail
+{
+
+  /**
+   * Exchange response details.  Full details are only included
+   * upon failure (HTTP status is not #MHD_HTTP_OK).
+   */
+  struct TALER_EXCHANGE_HttpResponse hr;
+
+  /**
+   * Coin this detail is about.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Refund transaction ID used.
+   */
+  uint64_t rtransaction_id;
+
+  /**
+   * Amount to be refunded for this coin.
+   */
+  struct TALER_Amount refund_amount;
+
+  /**
+   * Applicable refund transaction fee.
+   */
+  struct TALER_Amount refund_fee;
+
+  /**
+   * Public key of the exchange affirming the refund,
+   * only valid if the @e hr http_status is #MHD_HTTP_OK.
+   */
+  struct TALER_ExchangePublicKeyP exchange_pub;
+
+  /**
+   * Signature of the exchange affirming the refund,
+   * only valid if the @e hr http_status is #MHD_HTTP_OK.
+   */
+  struct TALER_ExchangeSignatureP exchange_sig;
+
+};
+
+
 /**
  * Callback to process a GET /refund request
  *
  * @param cls closure
  * @param hr HTTP response details
+ * @param h_contract_terms hash of the contract terms to which the refund is 
applied
+ * @param merchant_pub public key of the merchant
+ * @param num_details length of the @a details array
+ * @param details details about the refund processing
  */
 typedef void
 (*TALER_MERCHANT_RefundLookupCallback) (
   void *cls,
-  const struct TALER_MERCHANT_HttpResponse *hr);
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const struct GNUNET_HashCode *h_contract_terms,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  unsigned int num_details,
+  const struct TALER_MERCHANT_RefundDetail *details);
 
 
 /**
diff --git a/src/include/taler_merchantdb_plugin.h 
b/src/include/taler_merchantdb_plugin.h
index cc2e6bd..3ac4339 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -138,15 +138,17 @@ typedef void
  *
  * @param cls closure
  * @param coin_pub public coin from which the refund comes from
+ * @param exchange_url URL of the exchange that issued @a coin_pub
  * @param rtransaction_id identificator of the refund
  * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from coin_pub
+ * @param refund_amount refund amount which is being taken from @a coin_pub
  * @param refund_fee cost of this refund operation
  */
 typedef void
 (*TALER_MERCHANTDB_RefundCallback)(
   void *cls,
   const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const char *exchange_url,
   uint64_t rtransaction_id,
   const char *reason,
   const struct TALER_Amount *refund_amount,
diff --git a/src/lib/merchant_api_common.c b/src/lib/merchant_api_common.c
index 3317595..cb8de65 100644
--- a/src/lib/merchant_api_common.c
+++ b/src/lib/merchant_api_common.c
@@ -57,13 +57,13 @@ TALER_MERCHANT_parse_error_details_ (const json_t *response,
   hr->ec = TALER_JSON_get_error_code (response);
   hr->hint = TALER_JSON_get_error_hint (response);
 
-  /* handle 'exchange-http-status' */
+  /* handle 'exchange_http_status' */
   jc = json_object_get (response,
-                        "exchange-http-status");
+                        "exchange_http_status");
   /* The caller already knows that the JSON represents an error,
      so we are dealing with a missing error code here.  */
   if (NULL == jc)
-    return; /* no need to bother with exchange-code/hint if we had no status */
+    return; /* no need to bother with exchange_code/hint if we had no status */
   if (! json_is_integer (jc))
   {
     GNUNET_break_op (0);
@@ -71,9 +71,9 @@ TALER_MERCHANT_parse_error_details_ (const json_t *response,
   }
   hr->exchange_http_status = (unsigned int) json_integer_value (jc);
 
-  /* handle 'exchange-reply' */
+  /* handle 'exchange_reply' */
   jc = json_object_get (response,
-                        "exchange-reply");
+                        "exchange_reply");
   if (! json_is_object (jc))
   {
     GNUNET_break_op (0);
@@ -83,9 +83,9 @@ TALER_MERCHANT_parse_error_details_ (const json_t *response,
     hr->exchange_reply = jc;
   }
 
-  /* handle 'exchange-code' */
+  /* handle 'exchange_code' */
   jc = json_object_get (response,
-                        "exchange-code");
+                        "exchange_code");
   /* The caller already knows that the JSON represents an error,
      so we are dealing with a missing error code here.  */
   if (NULL == jc)
diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c
index 97a0b44..2bbc19c 100644
--- a/src/lib/merchant_api_pay.c
+++ b/src/lib/merchant_api_pay.c
@@ -298,7 +298,7 @@ check_conflict (struct TALER_MERCHANT_Pay *ph,
   json_t *ereply;
   struct TALER_CoinSpendPublicKeyP coin_pub;
   struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_json ("exchange-reply", &ereply),
+    GNUNET_JSON_spec_json ("exchange_reply", &ereply),
     GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
     GNUNET_JSON_spec_end ()
   };
diff --git a/src/lib/merchant_api_refund.c b/src/lib/merchant_api_refund.c
index a1a5715..c056c78 100644
--- a/src/lib/merchant_api_refund.c
+++ b/src/lib/merchant_api_refund.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015, 2016, 2017, 2019 Taler Systems SA
+  Copyright (C) 2014-2020 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
@@ -32,6 +32,9 @@
 #include <taler/taler_curl_lib.h>
 
 
+/**
+ * Handle to the refund lookup operation.
+ */
 struct TALER_MERCHANT_RefundLookupOperation
 {
   /**
@@ -81,6 +84,180 @@ TALER_MERCHANT_refund_lookup_cancel (
 }
 
 
+/**
+ * Check that the @a reply to the @a rlo is valid
+ *
+ * @param rlo lookup operation
+ * @param reply JSON reply to verify
+ * @return #TALER_EC_NONE if @a reply is well-formed
+ */
+static enum TALER_ErrorCode
+check_refund_result (struct TALER_MERCHANT_RefundLookupOperation *rlo,
+                     const json_t *reply)
+{
+  json_t *refunds;
+  unsigned int num_refunds;
+  struct GNUNET_HashCode h_contract_terms;
+  struct TALER_MerchantPublicKeyP merchant_pub;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_json ("refunds", &refunds),
+    GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &h_contract_terms),
+    GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub),
+    GNUNET_JSON_spec_end ()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (reply,
+                         spec,
+                         NULL, NULL))
+  {
+    GNUNET_break_op (0);
+    return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE;
+  }
+  num_refunds = json_array_size (refunds);
+  {
+    struct TALER_MERCHANT_RefundDetail rds[GNUNET_NZL (num_refunds)];
+    json_t *ercp[GNUNET_NZL (num_refunds)];
+
+    memset (rds,
+            0,
+            sizeof (rds));
+    memset (ercp,
+            0,
+            sizeof (ercp));
+    for (unsigned int i = 0; i<num_refunds; i++)
+    {
+      struct TALER_MERCHANT_RefundDetail *rd = &rds[i];
+      json_t *refund = json_array_get (refunds, i);
+      uint32_t hs;
+      struct GNUNET_JSON_Specification spec_detail[] = {
+        GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                     &rd->coin_pub),
+        TALER_JSON_spec_amount ("refund_amount",
+                                &rd->refund_amount),
+        TALER_JSON_spec_amount ("refund_fee",
+                                &rd->refund_fee),
+        GNUNET_JSON_spec_uint32 ("exchange_http_status",
+                                 &hs),
+        GNUNET_JSON_spec_uint64 ("rtransaction_id",
+                                 &rd->rtransaction_id),
+        GNUNET_JSON_spec_end ()
+      };
+
+      if (GNUNET_OK !=
+          GNUNET_JSON_parse (refund,
+                             spec_detail,
+                             NULL, NULL))
+      {
+        GNUNET_break_op (0);
+        GNUNET_JSON_parse_free (spec);
+        return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE;
+      }
+      rd->hr.http_status = (unsigned int) hs;
+    }
+
+    for (unsigned int i = 0; i<num_refunds; i++)
+    {
+      struct TALER_MERCHANT_RefundDetail *rd = &rds[i];
+      json_t *refund = json_array_get (refunds, i);
+
+      if (MHD_HTTP_OK == rd->hr.http_status)
+      {
+        struct GNUNET_JSON_Specification spec_detail[] = {
+          GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+                                       &rd->exchange_pub),
+          GNUNET_JSON_spec_fixed_auto ("exchange_sig",
+                                       &rd->exchange_sig),
+          GNUNET_JSON_spec_end ()
+        };
+
+        if (GNUNET_OK !=
+            GNUNET_JSON_parse (refund,
+                               spec_detail,
+                               NULL, NULL))
+        {
+          GNUNET_break_op (0);
+          for (unsigned int j = 0; j<i; j++)
+            if (NULL != ercp[j])
+              json_decref (ercp[j]);
+          GNUNET_JSON_parse_free (spec);
+          return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE;
+        }
+        /* verify exchange sig (we should not trust the merchant) */
+        {
+          struct TALER_RefundConfirmationPS depconf = {
+            .purpose.size = htonl (sizeof (depconf)),
+            .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND),
+            .h_contract_terms = h_contract_terms,
+            .coin_pub = rd->coin_pub,
+            .merchant = merchant_pub,
+            .rtransaction_id = GNUNET_htonll (rd->rtransaction_id)
+          };
+
+          TALER_amount_hton (&depconf.refund_amount,
+                             &rd->refund_amount);
+          TALER_amount_hton (&depconf.refund_fee,
+                             &rd->refund_fee);
+          if (GNUNET_OK !=
+              GNUNET_CRYPTO_eddsa_verify (
+                TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND,
+                &depconf,
+                &rd->exchange_sig.eddsa_signature,
+                &rd->exchange_pub.eddsa_pub))
+          {
+            /* While the *exchange* signature is invalid, we do blame the
+               merchant here, because the merchant should have checked and
+               sent us an error code (with exchange HTTP status code 0) instead
+               of claiming that the exchange yielded a good response. *///
+            GNUNET_break_op (0);
+            GNUNET_JSON_parse_free (spec);
+            return TALER_EC_REFUND_LOOKUP_INVALID_RESPONSE;
+          }
+        }
+      }
+      else
+      {
+        uint32_t ec;
+        struct GNUNET_JSON_Specification spec_detail[] = {
+          GNUNET_JSON_spec_uint32 ("exchange_code",
+                                   &ec),
+          GNUNET_JSON_spec_end ()
+        };
+
+        if (GNUNET_OK !=
+            GNUNET_JSON_parse (refund,
+                               spec_detail,
+                               NULL, NULL))
+        {
+          GNUNET_break_op (0);
+          rd->hr.ec = TALER_EC_INVALID;
+        }
+        ercp[i] = json_incref (json_object_get (refund,
+                                                "exchange_reply"));
+        rd->hr.reply = ercp[i];
+      }
+    }
+    {
+      struct TALER_MERCHANT_HttpResponse hr = {
+        .http_status = MHD_HTTP_OK,
+        .reply = reply
+      };
+      rlo->cb (rlo->cb_cls,
+               &hr,
+               &h_contract_terms,
+               &merchant_pub,
+               num_refunds,
+               rds);
+    }
+    for (unsigned int j = 0; j<num_refunds; j++)
+      if (NULL != ercp[j])
+        json_decref (ercp[j]);
+  }
+  GNUNET_JSON_parse_free (spec);
+  return TALER_EC_NONE;
+}
+
+
 /**
  * Process GET /refund response
  *
@@ -109,7 +286,15 @@ handle_refund_lookup_finished (void *cls,
     hr.ec = TALER_EC_INVALID_RESPONSE;
     break;
   case MHD_HTTP_OK:
-    /* nothing to do, all good! */
+    if (TALER_EC_NONE ==
+        (hr.ec = check_refund_result (rlo,
+                                      json)))
+    {
+      TALER_MERCHANT_refund_lookup_cancel (rlo);
+      return;
+    }
+    /* failure, report! */
+    hr.http_status = 0;
     break;
   case MHD_HTTP_NOT_FOUND:
     hr.ec = TALER_JSON_get_error_code (json);
@@ -122,7 +307,11 @@ handle_refund_lookup_finished (void *cls,
     break;
   }
   rlo->cb (rlo->cb_cls,
-           &hr);
+           &hr,
+           NULL,
+           NULL,
+           0,
+           NULL);
   TALER_MERCHANT_refund_lookup_cancel (rlo);
 }
 
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
index 5f366f0..f16cf5f 100644
--- a/src/lib/test_merchant_api.c
+++ b/src/lib/test_merchant_api.c
@@ -99,6 +99,11 @@ static struct GNUNET_CONTAINER_MultiHashMap 
*interned_strings;
  */
 #define USER_ACCOUNT_NAME "62"
 
+/**
+ * Account number of some other user.
+ */
+#define USER_ACCOUNT_NAME2 "63"
+
 /**
  * Account number used by the merchant
  */
@@ -232,7 +237,8 @@ run (void *cls,
      * Make a reserve exist,
      * according to the previous
      * transfer.
-     */cmd_exec_wirewatch ("wirewatch-1"),
+     *///
+    cmd_exec_wirewatch ("wirewatch-1"),
     TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2",
                                                  "EUR:10.02",
                                                  payer_payto,
@@ -445,24 +451,70 @@ run (void *cls,
   };
 
   struct TALER_TESTING_Command refund[] = {
-    TALER_TESTING_cmd_refund_increase ("refund-increase-1",
+    cmd_transfer_to_exchange ("create-reserve-1r",
+                              "EUR:10.02"),
+    /**
+     * Make a reserve exist, according to the previous transfer.
+     *///
+    cmd_exec_wirewatch ("wirewatch-1r"),
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2r",
+                                                 "EUR:10.02",
+                                                 payer_payto,
+                                                 exchange_payto,
+                                                 "create-reserve-1r"),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1r",
+                                       "create-reserve-1r",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2r",
+                                       "create-reserve-1r",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    /**
+     * Check the reserve is depleted.
+     */
+    TALER_TESTING_cmd_status ("withdraw-status-1r",
+                              "create-reserve-1r",
+                              "EUR:0",
+                              MHD_HTTP_OK),
+    TALER_TESTING_cmd_proposal ("create-proposal-1r",
+                                merchant_url,
+                                MHD_HTTP_OK,
+                                "{\"max_fee\":\"EUR:0.5\",\
+        \"order_id\":\"1r\",\
+        \"refund_deadline\": {\"t_ms\": 0},\
+        \"pay_deadline\": {\"t_ms\": \"never\" },\
+        \"amount\":\"EUR:5.0\",\
+        \"summary\": \"merchant-lib testcase\",\
+        \"fulfillment_url\": \"https://example.com/\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:5}\"} ] }"),
+    TALER_TESTING_cmd_pay ("pay-for-refund-1r",
+                           merchant_url,
+                           MHD_HTTP_OK,
+                           "create-proposal-1r",
+                           "withdraw-coin-1r",
+                           "EUR:5",
+                           "EUR:4.99",
+                           "EUR:0.01"),
+    TALER_TESTING_cmd_refund_increase ("refund-increase-1r",
                                        merchant_url,
                                        "refund test",
-                                       "1", /* order ID */
+                                       "1r", /* order ID */
                                        "EUR:0.1",
                                        "EUR:0.01",
                                        MHD_HTTP_OK),
     /* Ordinary refund.  */
-    TALER_TESTING_cmd_refund_lookup ("refund-lookup-1",
+    TALER_TESTING_cmd_refund_lookup ("refund-lookup-1r",
                                      merchant_url,
-                                     "refund-increase-1",
-                                     "deposit-simple",
-                                     "1",
+                                     "refund-increase-1r",
+                                     "pay-for-refund-1r",
+                                     "1r",
                                      MHD_HTTP_OK),
     /* Trying to pick up refund from non existent proposal.  */
     TALER_TESTING_cmd_refund_lookup ("refund-lookup-non-existent",
                                      merchant_url,
-                                     "refund-increase-1",
+                                     "refund-increase-1r",
                                      "deposit-simple",
                                      "non-existend-id",
                                      MHD_HTTP_NOT_FOUND),
@@ -522,7 +574,7 @@ run (void *cls,
                                 "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"unincreased-proposal\",\
         \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":9999999999999},\
+        \"pay_deadline\":{\"t_ms\":\"never\"},\
         \"amount\":\"EUR:5.0\",\
         \"summary\": \"merchant-lib testcase\",\
         \"fulfillment_url\": \"https://example.com/\",\
@@ -538,19 +590,19 @@ run (void *cls,
                            "EUR:0.01"),
     CMD_EXEC_AGGREGATOR ("run-aggregator-unincreased-refund"),
     TALER_TESTING_cmd_check_bank_transfer (
-      "check_bank_transfer-unincreased-refund",
+      "check_bank_transfer-paid-unincreased-refund",
       EXCHANGE_URL,
-      "EUR:4.98",
+      "EUR:9.88", /* '4.98 from above', plus 4.99 from 'pay-for-refund-1r'
+                     and MINUS 0.1 PLUS 0.01 (deposit fee) from 
'refund-increase-1r' */
       exchange_payto,
       merchant_payto),
-    /* Actually try to pick up the refund from the
-     * "unincreased proposal".  */
+    /* Actually try to pick up the refund from the "unincreased proposal".  */
     TALER_TESTING_cmd_refund_lookup_with_amount ("refund-lookup-unincreased",
                                                  merchant_url,
                                                  NULL,
                                                  "pay-unincreased-proposal",
                                                  "unincreased-proposal",
-                                                 MHD_HTTP_OK,
+                                                 MHD_HTTP_NOT_FOUND,
                                                  /* If a lookup is attempted
                                                   * on an unincreased
                                                   * proposal, the backend will
@@ -802,7 +854,7 @@ run (void *cls,
                          \"value\":\"{EUR:10}\"} ] }"),
     TALER_TESTING_cmd_pay ("pay-fail-partial-double-11-good",
                            merchant_url,
-                           MHD_HTTP_BAD_REQUEST,
+                           MHD_HTTP_NOT_ACCEPTABLE,
                            "create-proposal-11",
                            "withdraw-coin-11a",
                            "EUR:5",
@@ -865,7 +917,7 @@ run (void *cls,
                                              merchant_url,
                                              MHD_HTTP_OK,
                                              GNUNET_TIME_UNIT_ZERO_ABS,
-                                             4, /* Expected number of records 
*/
+                                             5, /* Expected number of records 
*/
                                              -100), /* Delta */
     /**
      * End the suite.  Fixme: better to have a label for this
diff --git a/src/lib/test_merchant_api_twisted.c 
b/src/lib/test_merchant_api_twisted.c
index a749fbf..e49e238 100644
--- a/src/lib/test_merchant_api_twisted.c
+++ b/src/lib/test_merchant_api_twisted.c
@@ -152,16 +152,14 @@ CMD_EXEC_WIREWATCH (const char *label)
 
 
 /**
- * Execute the taler-exchange-aggregator command with
+ * Execute the taler-exchange-aggregator, closer and transfer commands with
  * our configuration file.
  *
  * @param label label to use for the command.
  */
-static struct TALER_TESTING_Command
-CMD_EXEC_AGGREGATOR (const char *label)
-{
-  return TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE);
-}
+#define CMD_EXEC_AGGREGATOR(label) \
+  TALER_TESTING_cmd_exec_aggregator (label "-aggregator", CONFIG_FILE), \
+  TALER_TESTING_cmd_exec_transfer (label "-transfer", CONFIG_FILE)
 
 
 /**
@@ -173,7 +171,8 @@ CMD_EXEC_AGGREGATOR (const char *label)
  * @param url exchange_url
  */
 static struct TALER_TESTING_Command
-CMD_TRANSFER_TO_EXCHANGE (const char *label, const char *amount)
+CMD_TRANSFER_TO_EXCHANGE (const char *label,
+                          const char *amount)
 {
   return TALER_TESTING_cmd_admin_add_incoming (label,
                                                amount,
@@ -192,10 +191,8 @@ static void
 run (void *cls,
      struct TALER_TESTING_Interpreter *is)
 {
-
   /**** Triggering #5719 ****/
   struct TALER_TESTING_Command bug_5719[] = {
-
     /**
      * Move money to the exchange's bank account.
      */
@@ -206,30 +203,26 @@ run (void *cls,
      * transfer.
      */
     CMD_EXEC_WIREWATCH ("5719-wirewatch"),
-    TALER_TESTING_cmd_check_bank_admin_transfer
-      ("5719-check-transfer",
-      "EUR:1.01",
-      payer_payto,
-      exchange_payto,
-      "5719-create-reserve"),
-
+    TALER_TESTING_cmd_check_bank_admin_transfer ("5719-check-transfer",
+                                                 "EUR:1.01",
+                                                 payer_payto,
+                                                 exchange_payto,
+                                                 "5719-create-reserve"),
     TALER_TESTING_cmd_withdraw_amount ("5719-withdraw",
                                        "5719-create-reserve",
                                        "EUR:1",
                                        MHD_HTTP_OK),
-
     TALER_TESTING_cmd_status ("5719-reserve-status",
                               "5719-create-reserve",
                               "EUR:0",
                               MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal
-      ("5719-create-proposal",
-      twister_merchant_url,
-      MHD_HTTP_OK,
-      "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_proposal ("5719-create-proposal",
+                                twister_merchant_url,
+                                MHD_HTTP_OK,
+                                "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"5719TRIGGER\",\
         \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999},\
+        \"pay_deadline\":{\"t_ms\":\"never\"},\
         \"fulfillment_url\": \"https://example.com/\",\
         \"amount\":\"EUR:1.0\",\
         \"summary\": \"merchant-lib testcase\",\
@@ -242,13 +235,12 @@ run (void *cls,
      * not manage to pass the callback a valid JSON and will
      * instead pass a NULL pointer.  This should trigger the path
      * mentioned in the bug report #5719.
-     */TALER_TESTING_cmd_malform_response
-      ("5719-malform-exchange-resp",
-      PROXY_EXCHANGE_CONFIG_FILE),
-
+     *///
+    TALER_TESTING_cmd_malform_response ("5719-malform-exchange-resp",
+                                        PROXY_EXCHANGE_CONFIG_FILE),
     TALER_TESTING_cmd_pay ("5719-deposit",
                            twister_merchant_url,
-                           MHD_HTTP_SERVICE_UNAVAILABLE,
+                           MHD_HTTP_FAILED_DEPENDENCY,
                            "5719-create-proposal",
                            "5719-withdraw",
                            "EUR:1",
@@ -268,7 +260,7 @@ run (void *cls,
       "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"fail-check-payment-1\",\
         \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999},\
+        \"pay_deadline\":{\"t_ms\":\"never\"},\
         \"fulfillment_url\": \"https://example.com/\",\
         \"amount\":\"EUR:2.0\",\
         \"summary\": \"merchant-lib testcase\",\
@@ -276,41 +268,31 @@ run (void *cls,
                          \"value\":\"EUR:3\"} ] }"),
 
     /* Need any response code != 200.  */
-    TALER_TESTING_cmd_hack_response_code
-      ("non-200-response-code",
-      PROXY_MERCHANT_CONFIG_FILE,
-      MHD_HTTP_MULTIPLE_CHOICES),
-
-    TALER_TESTING_cmd_check_payment
-      ("check-payment-fail",
-      twister_merchant_url,
-      MHD_HTTP_MULTIPLE_CHOICES,
-      "proposal-for-check-payment",
-      GNUNET_SYSERR),  // any response != 200 gives "syserr"
-
+    TALER_TESTING_cmd_hack_response_code ("non-200-response-code",
+                                          PROXY_MERCHANT_CONFIG_FILE,
+                                          MHD_HTTP_MULTIPLE_CHOICES),
+    TALER_TESTING_cmd_check_payment ("check-payment-fail",
+                                     twister_merchant_url,
+                                     MHD_HTTP_MULTIPLE_CHOICES,
+                                     "proposal-for-check-payment",
+                                     GNUNET_SYSERR), // any response != 200 
gives "syserr"
     TALER_TESTING_cmd_delete_object ("hack-check-payment-0",
                                      PROXY_MERCHANT_CONFIG_FILE,
                                      "taler_pay_uri"),
-    TALER_TESTING_cmd_check_payment
-      ("check-payment-fail-invalid",
-      twister_merchant_url,
-      0,
-      "proposal-for-check-payment",
-      GNUNET_SYSERR),
-
-    TALER_TESTING_cmd_modify_object_dl
-      ("paid-true-for-unpaid",
-      PROXY_MERCHANT_CONFIG_FILE,
-      "paid",
-      "true"),
-
-    TALER_TESTING_cmd_check_payment
-      ("check-payment-fail-invalid-0",
-      twister_merchant_url,
-      0,
-      "proposal-for-check-payment",
-      GNUNET_SYSERR),
-
+    TALER_TESTING_cmd_check_payment ("check-payment-fail-invalid",
+                                     twister_merchant_url,
+                                     0,
+                                     "proposal-for-check-payment",
+                                     GNUNET_SYSERR),
+    TALER_TESTING_cmd_modify_object_dl ("paid-true-for-unpaid",
+                                        PROXY_MERCHANT_CONFIG_FILE,
+                                        "paid",
+                                        "true"),
+    TALER_TESTING_cmd_check_payment ("check-payment-fail-invalid-0",
+                                     twister_merchant_url,
+                                     0,
+                                     "proposal-for-check-payment",
+                                     GNUNET_SYSERR),
     TALER_TESTING_cmd_end ()
   };
 
@@ -321,49 +303,41 @@ run (void *cls,
      * Make the merchant return a 400 Bad Request response
      * due to uploaded body malformation.
      */
-    TALER_TESTING_cmd_malform_request
-      ("malform-order",
-      PROXY_MERCHANT_CONFIG_FILE),
-
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-0",
-      twister_merchant_url,
-      MHD_HTTP_BAD_REQUEST,
-      /* giving a valid JSON to not make it fail before
-       * data reaches the merchant.  */
-      "{\"not\": \"used\"}"),
-
-    TALER_TESTING_cmd_hack_response_code
-      ("proposal-500",
-      PROXY_MERCHANT_CONFIG_FILE,
-      MHD_HTTP_INTERNAL_SERVER_ERROR),
-
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-1",
-      twister_merchant_url,
-      /* This status code == 0 is gotten via a 500 Internal Server
-       * Error handed to the library.  */
-      MHD_HTTP_INTERNAL_SERVER_ERROR,
-      /* giving a valid JSON to not make it fail before
-       * data reaches the merchant.  */
-      "{\"not\": \"used\"}"),
+    TALER_TESTING_cmd_malform_request ("malform-order",
+                                       PROXY_MERCHANT_CONFIG_FILE),
+    TALER_TESTING_cmd_proposal ("create-proposal-0",
+                                twister_merchant_url,
+                                MHD_HTTP_BAD_REQUEST,
+                                /* giving a valid JSON to not make it fail 
before
+                                 * data reaches the merchant.  */
+                                "{\"not\": \"used\"}"),
+    TALER_TESTING_cmd_hack_response_code ("proposal-500",
+                                          PROXY_MERCHANT_CONFIG_FILE,
+                                          MHD_HTTP_INTERNAL_SERVER_ERROR),
+    TALER_TESTING_cmd_proposal ("create-proposal-1",
+                                twister_merchant_url,
+                                /* This status code == 0 is gotten via a 500 
Internal Server
+                                 * Error handed to the library.  */
+                                MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                /* giving a valid JSON to not make it fail 
before
+                                 * data reaches the merchant.  */
+                                "{\"not\": \"used\"}"),
 
     /**
      * Cause the PUT /proposal callback to be called
      * with a response code == 0.  We achieve this by malforming
      * the response body.
-     */TALER_TESTING_cmd_malform_response
-      ("malform-proposal",
-      PROXY_MERCHANT_CONFIG_FILE),
-
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-2",
-      twister_merchant_url,
-      0,
-      "{\"max_fee\":\"EUR:0.5\",\
+     *///
+    TALER_TESTING_cmd_malform_response ("malform-proposal",
+                                        PROXY_MERCHANT_CONFIG_FILE),
+
+    TALER_TESTING_cmd_proposal ("create-proposal-2",
+                                twister_merchant_url,
+                                0,
+                                "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"1\",\
         \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999},\
+        \"pay_deadline\":{\"t_ms\":\"never\"},\
         \"amount\":\"EUR:5.0\",\
         \"summary\": \"merchant-lib testcase\",\
         \"products\": [ {\"description\":\"ice cream\",\
@@ -375,15 +349,14 @@ run (void *cls,
     TALER_TESTING_cmd_delete_object ("remove-order-id",
                                      PROXY_MERCHANT_CONFIG_FILE,
                                      "order_id"),
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-3",
-      twister_merchant_url,
-      0,
-      "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_proposal ("create-proposal-3",
+                                twister_merchant_url,
+                                0,
+                                "{\"max_fee\":\"EUR:0.5\",\
          \"fulfillment_url\": \"https://example.com/\",\
          \"order_id\":\"2\",\
          \"refund_deadline\":{\"t_ms\":0},\
-         \"pay_deadline\":{\"t_ms\":99999999999},\
+         \"pay_deadline\":{\"t_ms\":\"never\"},\
          \"amount\":\"EUR:5.0\",\
          \"summary\": \"merchant-lib testcase\",\
          \"products\": [ {\"description\":\"ice cream\",\
@@ -392,11 +365,10 @@ run (void *cls,
      * Cause a 404 Not Found response code,
      * due to a non existing merchant instance.
      */
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-4",
-      twister_merchant_url_instance_nonexistent,
-      MHD_HTTP_NOT_FOUND,
-      "{\"amount\":\"EUR:5\",\
+    TALER_TESTING_cmd_proposal ("create-proposal-4",
+                                twister_merchant_url_instance_nonexistent,
+                                MHD_HTTP_NOT_FOUND,
+                                "{\"amount\":\"EUR:5\",\
          \"fulfillment_url\": \"https://example.com/\",\
          \"summary\": \"merchant-lib testcase\"}"),
 
@@ -426,14 +398,13 @@ run (void *cls,
 
     /* First step is to create a _valid_ proposal, so that
      * we can lookup for it later.  */
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-5",
-      twister_merchant_url,
-      MHD_HTTP_OK,
-      "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_proposal ("create-proposal-5",
+                                twister_merchant_url,
+                                MHD_HTTP_OK,
+                                "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"5\",\
         \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999},\
+        \"pay_deadline\":{\"t_ms\":\"never\"},\
         \"amount\":\"EUR:5.0\",\
         \"fulfillment_url\": \"https://example.com/\",\
         \"summary\": \"merchant-lib testcase\",\
@@ -464,14 +435,13 @@ run (void *cls,
      * code, that is then expected to trigger some
      * emergency behaviour, like setting the response
      * code to zero before calling the callback.
-     */TALER_TESTING_cmd_hack_response_code
-      ("twist-history",
-      PROXY_MERCHANT_CONFIG_FILE,
-      MHD_HTTP_GONE),
-
+     *///
+    TALER_TESTING_cmd_hack_response_code ("twist-history",
+                                          PROXY_MERCHANT_CONFIG_FILE,
+                                          MHD_HTTP_GONE),
     TALER_TESTING_cmd_history ("history-0",
                                twister_merchant_url,
-                               0,
+                               MHD_HTTP_GONE,
                                GNUNET_TIME_UNIT_ZERO_ABS,
                                1, // nresult
                                10, // start
@@ -480,9 +450,9 @@ run (void *cls,
      * Making the returned response malformed, in order
      * to make the JSON downloader+parser fail and call
      * the lib passing a response code as zero.
-     */TALER_TESTING_cmd_malform_response
-      ("malform-history",
-      PROXY_MERCHANT_CONFIG_FILE),
+     *///
+    TALER_TESTING_cmd_malform_response ("malform-history",
+                                        PROXY_MERCHANT_CONFIG_FILE),
 
     TALER_TESTING_cmd_history ("history-1",
                                twister_merchant_url,
@@ -500,64 +470,49 @@ run (void *cls,
    * This block tests whether a refund_deadline and/or
    * wire_transfer_deadline very far in the future do NOT
    * result in any wire transfer from the aggregator (#5366).
-   */struct TALER_TESTING_Command unaggregation[] = {
-
-    CMD_TRANSFER_TO_EXCHANGE
-      ("create-reserve-unaggregation",
-      "EUR:5.01"),
-
-    CMD_EXEC_WIREWATCH
-      ("wirewatch-unaggregation"),
-    TALER_TESTING_cmd_check_bank_admin_transfer
-      ("check_bank_transfer-unaggregation",
+   *///
+  struct TALER_TESTING_Command unaggregation[] = {
+    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-unaggregation",
+                              "EUR:5.01"),
+    CMD_EXEC_WIREWATCH ("wirewatch-unaggregation"),
+    TALER_TESTING_cmd_check_bank_admin_transfer (
+      "check_bank_transfer-unaggregation",
       "EUR:5.01",
       payer_payto,
       exchange_payto,
       "create-reserve-unaggregation"),
-
-    TALER_TESTING_cmd_check_bank_empty
-      ("check_bank_unaggregated-a"),
-
-    TALER_TESTING_cmd_withdraw_amount
-      ("withdraw-coin-unaggregation",
-      "create-reserve-unaggregation",
-      "EUR:5",
-      MHD_HTTP_OK),
-
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-unaggregation",
-      /* Need a fresh instance in order to associate this
-       * proposal with a fresh h_wire;  this way, this proposal
-       * won't get hooked by the aggregator gathering same-h_wire'd
-       * transactions.  */
-      twister_merchant_url_instance_tor,
-      MHD_HTTP_OK,
-      "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_unaggregated-a"),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-unaggregation",
+                                       "create-reserve-unaggregation",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_proposal ("create-proposal-unaggregation",
+                                /* Need a fresh instance in order to associate 
this
+                                 * proposal with a fresh h_wire;  this way, 
this proposal
+                                 * won't get hooked by the aggregator 
gathering same-h_wire'd
+                                 * transactions.  */
+                                twister_merchant_url_instance_tor,
+                                MHD_HTTP_OK,
+                                "{\"max_fee\":\"EUR:0.5\",\
         \"refund_deadline\":{\"t_ms\":2000},\
-        \"pay_deadline\":{\"t_ms\":1000},\
+        \"pay_deadline\":{\"t_ms\":2366841500000},\
         \"wire_transfer_deadline\":{\"t_ms\":2366841600000},\
         \"amount\":\"EUR:0.5\",\
         \"summary\": \"unaggregated product\",\
         \"fulfillment_url\": \"https://example.com/\",\
         \"products\": [ {\"description\":\"unaggregated cream\",\
                          \"value\":\"{EUR:5}\"} ] }"),
-
-    TALER_TESTING_cmd_pay
-      ("pay-unaggregation",
-      twister_merchant_url_instance_tor,
-      MHD_HTTP_OK,
-      "create-proposal-unaggregation",
-      "withdraw-coin-unaggregation",
-      "EUR:5",  // amount + fee
-      "EUR:4.99",  // amount - fee
-      "EUR:0.01"),  // refund fee
-
-    CMD_EXEC_AGGREGATOR
-      ("aggregation-attempt"),
-
+    TALER_TESTING_cmd_pay ("pay-unaggregation",
+                           twister_merchant_url_instance_tor,
+                           MHD_HTTP_OK,
+                           "create-proposal-unaggregation",
+                           "withdraw-coin-unaggregation",
+                           "EUR:5", // amount + fee
+                           "EUR:4.99", // amount - fee
+                           "EUR:0.01"), // refund fee
+    CMD_EXEC_AGGREGATOR ("aggregation-attempt"),
     /* Make sure NO aggregation took place.  */
-    TALER_TESTING_cmd_check_bank_empty
-      ("check_bank_unaggregated-b"),
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_unaggregated-b"),
 
     TALER_TESTING_cmd_end ()
   };
@@ -567,30 +522,26 @@ run (void *cls,
     CMD_TRANSFER_TO_EXCHANGE ("create-reserve-5383",
                               "EUR:2.02"),
     CMD_EXEC_WIREWATCH ("wirewatch-5383"),
-    TALER_TESTING_cmd_check_bank_admin_transfer
-      ("check_bank_transfer-5383",
-      "EUR:2.02",
-      payer_payto,
-      exchange_payto,
-      "create-reserve-5383"),
-    TALER_TESTING_cmd_withdraw_amount
-      ("withdraw-coin-5383a",
-      "create-reserve-5383",
-      "EUR:1",
-      MHD_HTTP_OK),
-    TALER_TESTING_cmd_withdraw_amount
-      ("withdraw-coin-5383b",
-      "create-reserve-5383",
-      "EUR:1",
-      MHD_HTTP_OK),
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-5383",
-      twister_merchant_url,
-      MHD_HTTP_OK,
-      "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-5383",
+                                                 "EUR:2.02",
+                                                 payer_payto,
+                                                 exchange_payto,
+                                                 "create-reserve-5383"),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-5383a",
+                                       "create-reserve-5383",
+                                       "EUR:1",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-5383b",
+                                       "create-reserve-5383",
+                                       "EUR:1",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_proposal ("create-proposal-5383",
+                                twister_merchant_url,
+                                MHD_HTTP_OK,
+                                "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"5383\",\
         \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999},\
+        \"pay_deadline\":{\"t_ms\":\"never\"},\
         \"fulfillment_url\": \"https://example.com/\",\
         \"amount\":\"EUR:2.0\",\
         \"summary\": \"merchant-lib testcase\",\
@@ -606,27 +557,23 @@ run (void *cls,
                            "EUR:1.99", // no sense now
                            "EUR:0.01"), // no sense now
     CMD_EXEC_AGGREGATOR ("run-aggregator-5383"),
-    TALER_TESTING_cmd_check_bank_transfer
-      ("check_aggregation_transfer-5383",
-      twister_exchange_url,
-      /* paid,         1.97 =
-         brutto        2.00 -
-         deposit fee   0.01 * 2 -
-         wire fee      0.01
-      */"EUR:1.97",
-      exchange_payto,
-      merchant_payto),
-    TALER_TESTING_cmd_modify_object_dl
-      ("hack-5383",
-      PROXY_EXCHANGE_CONFIG_FILE,
-      "total",
-      "EUR:0.98"),
-    TALER_TESTING_cmd_merchant_track_transfer
-      ("track-5383",
-      twister_merchant_url,
-      MHD_HTTP_FAILED_DEPENDENCY,
-      "check_aggregation_transfer-5383"),
-
+    TALER_TESTING_cmd_check_bank_transfer ("check_aggregation_transfer-5383",
+                                           twister_exchange_url,
+                                           /* paid,         1.97 =
+                                              brutto        2.00 -
+                                              deposit fee   0.01 * 2 -
+                                              wire fee      0.01
+                                           */"EUR:1.97",
+                                           exchange_payto,
+                                           merchant_payto),
+    TALER_TESTING_cmd_modify_object_dl ("hack-5383",
+                                        PROXY_EXCHANGE_CONFIG_FILE,
+                                        "total",
+                                        "EUR:0.98"),
+    TALER_TESTING_cmd_merchant_track_transfer ("track-5383",
+                                               twister_merchant_url,
+                                               MHD_HTTP_FAILED_DEPENDENCY,
+                                               
"check_aggregation_transfer-5383"),
     TALER_TESTING_cmd_end ()
   };
 
@@ -645,17 +592,12 @@ run (void *cls,
      * transfer.
      */
     CMD_EXEC_WIREWATCH ("wirewatch-1"),
-
-    TALER_TESTING_cmd_check_bank_admin_transfer
-      ("check_bank_transfer-2",
-      "EUR:2.02",
-      payer_payto,
-      exchange_payto,
-      "create-reserve-1"),
-
-    TALER_TESTING_cmd_check_bank_empty
-      ("track_chunk_check_empty-a"),
-
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2",
+                                                 "EUR:2.02",
+                                                 payer_payto,
+                                                 exchange_payto,
+                                                 "create-reserve-1"),
+    TALER_TESTING_cmd_check_bank_empty ("track_chunk_check_empty-a"),
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
                                        "create-reserve-1",
                                        "EUR:1",
@@ -668,15 +610,13 @@ run (void *cls,
                               "create-reserve-1",
                               "EUR:0",
                               MHD_HTTP_OK),
-
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-6",
-      twister_merchant_url,
-      MHD_HTTP_OK,
-      "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_proposal ("create-proposal-6",
+                                twister_merchant_url,
+                                MHD_HTTP_OK,
+                                "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"11\",\
         \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999},\
+        \"pay_deadline\":{\"t_ms\":\"never\"},\
         \"fulfillment_url\": \"https://example.com/\",\
         \"amount\":\"EUR:2.0\",\
         \"summary\": \"merchant-lib testcase\",\
@@ -688,7 +628,6 @@ run (void *cls,
                                      MHD_HTTP_OK,
                                      "create-proposal-6",
                                      GNUNET_NO),
-
     TALER_TESTING_cmd_pay ("deposit-simple",
                            twister_merchant_url,
                            MHD_HTTP_OK,
@@ -698,23 +637,22 @@ run (void *cls,
                            "EUR:2",
                            "EUR:1.99", // no sense now
                            "EUR:0.01"), // no sense now
-
     TALER_TESTING_cmd_check_payment ("check-payment-2",
                                      twister_merchant_url,
                                      MHD_HTTP_OK,
                                      "create-proposal-6",
                                      GNUNET_YES),
     CMD_EXEC_AGGREGATOR ("run-aggregator"),
-    TALER_TESTING_cmd_check_bank_transfer
-      ("check_bank_transfer-1",
-      twister_exchange_url,  /* has the 8888-port thing.  */
-      /* paid,         1.97 =
-         brutto        2.00 -
-         deposit fee   0.01 * 2 -
-         wire fee      0.01
-      */"EUR:1.97",
-      exchange_payto,
-      merchant_payto),
+    TALER_TESTING_cmd_check_bank_transfer ("check_bank_transfer-1",
+                                           twister_exchange_url, /* has the 
8888-port thing.  */
+                                           /* paid,         1.97 =
+                                              brutto        2.00 -
+                                              deposit fee   0.01 * 2 -
+                                              wire fee      0.01
+                                           *///
+                                           "EUR:1.97",
+                                           exchange_payto,
+                                           merchant_payto),
 
     /**
      * Fake total to include only one coin.  Math: each 1-EUR
@@ -727,21 +665,18 @@ run (void *cls,
      * In particular, they are supposed to modify the call
      * to /track/transfer issued from the merchant to the
      * exchange that happens _before_ the call to /track/transaction
-     * issued below by the test case (to the merchant backend.) 
*/TALER_TESTING_cmd_modify_object_dl
-      ("hack-0",
-      PROXY_EXCHANGE_CONFIG_FILE,
-      "total",
-      "EUR:0.98"),
-    TALER_TESTING_cmd_delete_object
-      ("hack-1",
-      PROXY_EXCHANGE_CONFIG_FILE,
-      "deposits.0"),
-    TALER_TESTING_cmd_merchant_track_transaction
-      ("track-transaction-1",
-      twister_merchant_url,
-      MHD_HTTP_FAILED_DEPENDENCY,
-      "deposit-simple"),
-
+     * issued below by the test case (to the merchant backend.) *///
+    TALER_TESTING_cmd_modify_object_dl ("hack-0",
+                                        PROXY_EXCHANGE_CONFIG_FILE,
+                                        "total",
+                                        "EUR:0.98"),
+    TALER_TESTING_cmd_delete_object ("hack-1",
+                                     PROXY_EXCHANGE_CONFIG_FILE,
+                                     "deposits.0"),
+    TALER_TESTING_cmd_merchant_track_transaction ("track-transaction-1",
+                                                  twister_merchant_url,
+                                                  MHD_HTTP_FAILED_DEPENDENCY,
+                                                  "deposit-simple"),
     TALER_TESTING_cmd_end ()
   };
 
@@ -756,42 +691,34 @@ run (void *cls,
                               "EUR:1.01"),
 
     /**
-     * Make a reserve exist, according to the previous
-     * transfer.
+     * Make a reserve exist, according to the previous transfer.
      */
     CMD_EXEC_WIREWATCH ("wirewatch-abort-1"),
-
-    TALER_TESTING_cmd_check_bank_admin_transfer
-      ("check_bank_transfer-abort-1",
-      "EUR:1.01",
-      payer_payto,
-      exchange_payto,
-      "create-reserve-abort-1"),
-
+    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-abort-1",
+                                                 "EUR:1.01",
+                                                 payer_payto,
+                                                 exchange_payto,
+                                                 "create-reserve-abort-1"),
     TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-abort-1",
                                        "create-reserve-abort-1",
                                        "EUR:1",
                                        MHD_HTTP_OK),
-
     TALER_TESTING_cmd_status ("withdraw-status-abort-1",
                               "create-reserve-abort-1",
                               "EUR:0",
                               MHD_HTTP_OK),
-
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-abort-1",
-      twister_merchant_url,
-      MHD_HTTP_OK,
-      "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_proposal ("create-proposal-abort-1",
+                                twister_merchant_url,
+                                MHD_HTTP_OK,
+                                "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"abort-one\",\
         \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999},\
+        \"pay_deadline\":{\"t_ms\":\"never\"},\
         \"fulfillment_url\": \"https://example.com/\",\
         \"amount\":\"EUR:3.0\",\
         \"summary\": \"merchant-lib testcase\",\
         \"products\": [ {\"description\":\"ice cream\",\
                          \"value\":\"{EUR:3}\"} ] }"),
-
     /* Will only pay _half_ the supposed price,
      * so we'll then have the right to abort.  */
     TALER_TESTING_cmd_pay ("deposit-simple-for-abort",
@@ -802,94 +729,73 @@ run (void *cls,
                            "EUR:1",
                            "EUR:1.99", // no sense now
                            "EUR:0.01"), // no sense now
-
     TALER_TESTING_cmd_delete_object ("hack-abort-1",
                                      PROXY_MERCHANT_CONFIG_FILE,
                                      "merchant_pub"),
-
     TALER_TESTING_cmd_pay_abort ("pay-abort-1",
                                  twister_merchant_url,
                                  "deposit-simple-for-abort",
                                  0),
-
-    TALER_TESTING_cmd_delete_object
-      ("hack-abort-2",
-      PROXY_MERCHANT_CONFIG_FILE,
-      "refund_permissions.0.rtransaction_id"),
-
+    TALER_TESTING_cmd_delete_object ("hack-abort-2",
+                                     PROXY_MERCHANT_CONFIG_FILE,
+                                     "refund_permissions.0.rtransaction_id"),
     TALER_TESTING_cmd_pay_abort ("pay-abort-2",
                                  twister_merchant_url,
                                  "deposit-simple-for-abort",
                                  0),
-
-    TALER_TESTING_cmd_modify_object_dl
-      ("hack-abort-3",
-      PROXY_MERCHANT_CONFIG_FILE,
-      "refund_permissions.0.coin_pub",
-      /* dummy coin.  */
-      "8YX10E41ZWHX0X2RK4XFAXB2D3M05M1HNG14ZFZZB8M7SA4QCKCG"),
-
+    TALER_TESTING_cmd_modify_object_dl ("hack-abort-3",
+                                        PROXY_MERCHANT_CONFIG_FILE,
+                                        "refund_permissions.0.coin_pub",
+                                        /* dummy coin.  */
+                                        
"8YX10E41ZWHX0X2RK4XFAXB2D3M05M1HNG14ZFZZB8M7SA4QCKCG"),
     TALER_TESTING_cmd_pay_abort ("pay-abort-3",
                                  twister_merchant_url,
                                  "deposit-simple-for-abort",
                                  0),
-
-    TALER_TESTING_cmd_flip_download
-      ("hack-abort-4",
-      PROXY_MERCHANT_CONFIG_FILE,
-      "refund_permissions.0.merchant_sig"),
-
+    TALER_TESTING_cmd_flip_download ("hack-abort-4",
+                                     PROXY_MERCHANT_CONFIG_FILE,
+                                     "refund_permissions.0.merchant_sig"),
     TALER_TESTING_cmd_pay_abort ("pay-abort-4",
                                  twister_merchant_url,
                                  "deposit-simple-for-abort",
                                  0),
     /* just malforming the response.  */
-    TALER_TESTING_cmd_malform_response
-      ("malform-abortion",
-      PROXY_MERCHANT_CONFIG_FILE),
-
+    TALER_TESTING_cmd_malform_response ("malform-abortion",
+                                        PROXY_MERCHANT_CONFIG_FILE),
     TALER_TESTING_cmd_pay_abort ("pay-abort-5",
                                  twister_merchant_url,
                                  "deposit-simple-for-abort",
                                  0),
-
     CMD_TRANSFER_TO_EXCHANGE ("create-reserve-double-spend",
                               "EUR:1.01"),
-
     CMD_EXEC_WIREWATCH ("wirewatch-double-spend"),
-
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-double-spend",
-      twister_merchant_url,
-      MHD_HTTP_OK,
-      "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_proposal ("create-proposal-double-spend",
+                                twister_merchant_url,
+                                MHD_HTTP_OK,
+                                "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"DS-1\",\
         \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999},\
+        \"pay_deadline\":{\"t_ms\":\"never\"},\
         \"fulfillment_url\": \"https://example.com/\",\
         \"amount\":\"EUR:1.0\",\
         \"summary\": \"merchant-lib testcase\",\
         \"products\": [ {\"description\": \"will succeed\"}] }"),
-
-    TALER_TESTING_cmd_proposal
-      ("create-proposal-double-spend-1",
-      twister_merchant_url,
-      MHD_HTTP_OK,
-      "{\"max_fee\":\"EUR:0.5\",\
+    TALER_TESTING_cmd_proposal ("create-proposal-double-spend-1",
+                                twister_merchant_url,
+                                MHD_HTTP_OK,
+                                "{\"max_fee\":\"EUR:0.5\",\
         \"order_id\":\"DS-2\",\
         \"refund_deadline\":{\"t_ms\":0},\
-        \"pay_deadline\":{\"t_ms\":99999999999},\
+        \"pay_deadline\":{\"t_ms\":\"never\"},\
         \"fulfillment_url\": \"https://example.com/\",\
         \"amount\":\"EUR:1.0\",\
         \"summary\": \"merchant-lib testcase\",\
         \"products\": [ {\"description\": \"will fail\"}] }"),
 
-    TALER_TESTING_cmd_withdraw_amount
-      ("withdraw-coin-double-spend",
-      "create-reserve-double-spend",
-      "EUR:1",
-      MHD_HTTP_OK),
-
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-double-spend",
+                                       "create-reserve-double-spend",
+                                       "EUR:1",
+                                       MHD_HTTP_OK),
     TALER_TESTING_cmd_pay ("deposit-simple-ok",
                            twister_merchant_url,
                            MHD_HTTP_OK,
@@ -898,57 +804,40 @@ run (void *cls,
                            "EUR:1",
                            "EUR:1.99", // no sense now
                            "EUR:0.01"), // no sense now
-
-    TALER_TESTING_cmd_flip_download
-      ("hack-coin-history",
-      PROXY_MERCHANT_CONFIG_FILE,
-      "history.0.coin_sig"),
-
+    TALER_TESTING_cmd_flip_download ("hack-coin-history",
+                                     PROXY_MERCHANT_CONFIG_FILE,
+                                     "history.0.coin_sig"),
     /* Coin history check will fail,
      * due to coin's bad signature.  */
     TALER_TESTING_cmd_pay ("deposit-simple-fail",
                            twister_merchant_url,
-                           0,
+                           MHD_HTTP_CONFLICT,
                            "create-proposal-double-spend-1",
                            "withdraw-coin-double-spend",
                            "EUR:1",
                            "EUR:1.99", // no sense now
                            "EUR:0.01"), // no sense now
-
     /* max uint64 number: 9223372036854775807; try to overflow! */
-
     TALER_TESTING_cmd_end ()
   };
 
   struct TALER_TESTING_Command commands[] = {
-
     TALER_TESTING_cmd_batch ("check-payment",
                              check_payment),
-
     TALER_TESTING_cmd_batch ("proposal",
                              proposal),
-
     TALER_TESTING_cmd_batch ("history",
                              history),
-
     TALER_TESTING_cmd_batch ("unaggregation",
                              unaggregation),
-
     TALER_TESTING_cmd_batch ("track",
                              track),
-
     TALER_TESTING_cmd_batch ("track-5383",
                              track_5383),
-
     TALER_TESTING_cmd_batch ("pay",
                              pay),
-
     TALER_TESTING_cmd_batch ("bug-5719",
                              bug_5719),
-    /**
-     * End the suite.  Fixme: better to have a label for this
-     * too, as it shows a "(null)" token on logs.
-     */
     TALER_TESTING_cmd_end ()
   };
 
@@ -1013,6 +902,7 @@ main (int argc,
   TALER_TESTING_cleanup_files (CONFIG_FILE);
 
   switch (TALER_TESTING_prepare_exchange (CONFIG_FILE,
+                                          GNUNET_YES,
                                           &ec))
   {
   case GNUNET_SYSERR:
diff --git a/src/lib/testing_api_cmd_check_payment.c 
b/src/lib/testing_api_cmd_check_payment.c
index f72ab92..8d59129 100644
--- a/src/lib/testing_api_cmd_check_payment.c
+++ b/src/lib/testing_api_cmd_check_payment.c
@@ -418,6 +418,7 @@ check_payment_conclude_cleanup (void *cls,
     GNUNET_SCHEDULER_cancel (cps->task);
     cps->task = NULL;
   }
+  GNUNET_free (cps);
 }
 
 
diff --git a/src/lib/testing_api_cmd_history.c 
b/src/lib/testing_api_cmd_history.c
index b87a604..dabbf3c 100644
--- a/src/lib/testing_api_cmd_history.c
+++ b/src/lib/testing_api_cmd_history.c
@@ -109,10 +109,9 @@ history_cb (void *cls,
   if (hs->http_status != hr->http_status)
     TALER_TESTING_FAIL (hs->is);
 
-  if (0 == hs->http_status)
+  if (MHD_HTTP_OK != hs->http_status)
   {
-    /* 0 was caused intentionally by the tests,
-     * move on without further checking. */
+    /* move on without further checking. */
     TALER_TESTING_interpreter_next (hs->is);
     return;
   }
@@ -277,15 +276,16 @@ cmd_history2 (const char *label,
   hs->nrows = nrows;
   hs->merchant_url = merchant_url;
   hs->use_default_start = use_default_start;
-
-  struct TALER_TESTING_Command cmd = {
-    .cls = hs,
-    .label = label,
-    .run = &history_run,
-    .cleanup = &history_cleanup
-  };
-
-  return cmd;
+  {
+    struct TALER_TESTING_Command cmd = {
+      .cls = hs,
+      .label = label,
+      .run = &history_run,
+      .cleanup = &history_cleanup
+    };
+
+    return cmd;
+  }
 }
 
 
diff --git a/src/lib/testing_api_cmd_pay_abort_refund.c 
b/src/lib/testing_api_cmd_pay_abort_refund.c
index 0d811c1..7ecf8b7 100644
--- a/src/lib/testing_api_cmd_pay_abort_refund.c
+++ b/src/lib/testing_api_cmd_pay_abort_refund.c
@@ -85,11 +85,13 @@ struct PayAbortRefundState
  * @param cls closure
  * @param hr HTTP response code details
  * @param sign_key exchange key used to sign @a obj, or NULL
+ * @param signature the actual signature, or NULL on error
  */
 static void
 abort_refund_cb (void *cls,
                  const struct TALER_EXCHANGE_HttpResponse *hr,
-                 const struct TALER_ExchangePublicKeyP *sign_key)
+                 const struct TALER_ExchangePublicKeyP *sign_key,
+                 const struct TALER_ExchangeSignatureP *signature)
 {
   struct PayAbortRefundState *pars = cls;
 
diff --git a/src/lib/testing_api_cmd_poll_payment.c 
b/src/lib/testing_api_cmd_poll_payment.c
index 954b1d4..6d8c37c 100644
--- a/src/lib/testing_api_cmd_poll_payment.c
+++ b/src/lib/testing_api_cmd_poll_payment.c
@@ -391,6 +391,7 @@ poll_payment_conclude_cleanup (void *cls,
     GNUNET_SCHEDULER_cancel (cps->task);
     cps->task = NULL;
   }
+  GNUNET_free (cps);
 }
 
 
diff --git a/src/lib/testing_api_cmd_proposal.c 
b/src/lib/testing_api_cmd_proposal.c
index 1d99c1a..fd53db2 100644
--- a/src/lib/testing_api_cmd_proposal.c
+++ b/src/lib/testing_api_cmd_proposal.c
@@ -215,8 +215,9 @@ proposal_cb (void *cls,
   ps->po = NULL;
   if (ps->http_status != hr->http_status)
   {
-    TALER_LOG_ERROR ("Given vs expected: %u vs %u\n",
+    TALER_LOG_ERROR ("Given vs expected: %u(%d) vs %u\n",
                      hr->http_status,
+                     (int) hr->ec,
                      ps->http_status);
     TALER_TESTING_FAIL (ps->is);
   }
diff --git a/src/lib/testing_api_cmd_refund_increase.c 
b/src/lib/testing_api_cmd_refund_increase.c
index 153d8a7..da2c70b 100644
--- a/src/lib/testing_api_cmd_refund_increase.c
+++ b/src/lib/testing_api_cmd_refund_increase.c
@@ -114,7 +114,14 @@ refund_increase_cb (void *cls,
 
   ris->rio = NULL;
   if (ris->http_code != hr->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Expected status %u, got %u(%d) for refund increase\n",
+                ris->http_code,
+                hr->http_status,
+                (int) hr->ec);
     TALER_TESTING_FAIL (ris->is);
+  }
   TALER_TESTING_interpreter_next (ris->is);
 }
 
diff --git a/src/lib/testing_api_cmd_refund_lookup.c 
b/src/lib/testing_api_cmd_refund_lookup.c
index c677fe5..7db933a 100644
--- a/src/lib/testing_api_cmd_refund_lookup.c
+++ b/src/lib/testing_api_cmd_refund_lookup.c
@@ -103,27 +103,6 @@ refund_lookup_cleanup (void *cls,
 }
 
 
-/**
- * Callback that frees all the elements in the hashmap
- *
- * @param cls closure, NULL
- * @param key current key
- * @param value a `struct TALER_Amount`
- *
- * @return always #GNUNET_YES (continue to iterate)
- */
-static int
-hashmap_free (void *cls,
-              const struct GNUNET_HashCode *key,
-              void *value)
-{
-  struct TALER_Amount *refund_amount = value;
-
-  GNUNET_free (refund_amount);
-  return GNUNET_YES;
-}
-
-
 /**
  * Process "GET /public/refund" (lookup) response;
  * mainly checking if the refunded amount matches the
@@ -131,172 +110,203 @@ hashmap_free (void *cls,
  *
  * @param cls closure
  * @param hr HTTP response we got
+ * @param h_contract_terms hash of the contract terms to which the refund is 
applied
+ * @param merchant_pub public key of the merchant
+ * @param num_details length of the @a details array
+ * @param details details about the refund processing
  */
 static void
 refund_lookup_cb (void *cls,
-                  const struct TALER_MERCHANT_HttpResponse *hr)
+                  const struct TALER_MERCHANT_HttpResponse *hr,
+                  const struct GNUNET_HashCode *h_contract_terms,
+                  const struct TALER_MerchantPublicKeyP *merchant_pub,
+                  unsigned int num_details,
+                  const struct TALER_MERCHANT_RefundDetail *details)
 {
   struct RefundLookupState *rls = cls;
   struct GNUNET_CONTAINER_MultiHashMap *map;
-  size_t index;
-  json_t *elem;
-  const char *error_name;
-  unsigned int error_line;
-  struct GNUNET_HashCode h_coin_pub;
   const char *coin_reference;
-  char *coin_reference_dup;
   const char *icoin_reference;
-  const struct TALER_TESTING_Command *pay_cmd;
-  const struct TALER_TESTING_Command *increase_cmd;
   const char *refund_amount;
   struct TALER_Amount acc;
   struct TALER_Amount ra;
-  const json_t *arr;
 
   rls->rlo = NULL;
+  if (MHD_HTTP_GONE == rls->http_code)
+  {
+    /* special case: GONE is not the top-level code, but expected INSIDE the 
details */
+    if (MHD_HTTP_OK != hr->http_status)
+      TALER_TESTING_FAIL (rls->is);
+    for (unsigned int i = 0; i<num_details; i++)
+      if (MHD_HTTP_GONE != details[i].hr.http_status)
+        TALER_TESTING_FAIL (rls->is);
+    /* all good */
+    TALER_TESTING_interpreter_next (rls->is);
+    return;
+  }
   if (rls->http_code != hr->http_status)
     TALER_TESTING_FAIL (rls->is);
-
-  arr = json_object_get (hr->reply,
-                         "refund_permissions");
-  if (NULL == arr)
+  if (MHD_HTTP_OK != hr->http_status)
   {
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Tolerating a refund permission not found\n");
     TALER_TESTING_interpreter_next (rls->is);
     return;
   }
   map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
-
   /* Put in array every refunded coin.  */
-  json_array_foreach (arr, index, elem)
+  for (unsigned int i = 0; i<num_details; i++)
   {
-    struct TALER_CoinSpendPublicKeyP coin_pub;
-    struct TALER_Amount *irefund_amount = GNUNET_new
-                                            (struct TALER_Amount);
-    struct GNUNET_JSON_Specification spec[] = {
-      GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
-      TALER_JSON_spec_amount ("refund_amount", irefund_amount),
-      GNUNET_JSON_spec_end ()
-    };
+    struct GNUNET_HashCode h_coin_pub;
 
-    GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (elem,
-                                                   spec,
-                                                   &error_name,
-                                                   &error_line));
-    GNUNET_CRYPTO_hash (&coin_pub,
+    if (MHD_HTTP_OK != details[i].hr.http_status)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Got unexpected status %u/%d for refunded coin %u\n",
+                  details[i].hr.http_status,
+                  (int) details[i].hr.ec,
+                  i);
+      GNUNET_CONTAINER_multihashmap_destroy (map);
+      TALER_TESTING_FAIL (rls->is);
+      return;
+    }
+    TALER_LOG_DEBUG ("Coin %s refund is %s\n",
+                     TALER_B2S (&details[i].coin_pub),
+                     TALER_amount2s (&details[i].refund_amount));
+    GNUNET_CRYPTO_hash (&details[i].coin_pub,
                         sizeof (struct TALER_CoinSpendPublicKeyP),
                         &h_coin_pub);
-    GNUNET_assert (GNUNET_OK ==
-                   GNUNET_CONTAINER_multihashmap_put (
-                     map,
-                     &h_coin_pub, // which
-                     irefund_amount, // how much
-                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-  };
+    if (GNUNET_OK !=
+        GNUNET_CONTAINER_multihashmap_put (
+          map,
+          &h_coin_pub,
+          (void *) &details[i],
+          GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
+    {
+      GNUNET_CONTAINER_multihashmap_destroy (map);
+      TALER_TESTING_FAIL (rls->is);
+    }
+  }
 
   /* Compare spent coins with refunded, and if they match,
    * increase an accumulator.  */
-  if (NULL == (pay_cmd = TALER_TESTING_interpreter_lookup_command (
-                 rls->is,
-                 rls->pay_reference)))
-    TALER_TESTING_FAIL (rls->is);
-
-  if (GNUNET_OK !=
-      TALER_TESTING_get_trait_coin_reference (
-        pay_cmd,
-        0,
-        &coin_reference))
-    TALER_TESTING_FAIL (rls->is);
-
-  GNUNET_assert (GNUNET_OK ==
-                 TALER_amount_get_zero ("EUR",
-                                        &acc));
-  coin_reference_dup = GNUNET_strdup (coin_reference);
-  for (icoin_reference = strtok (coin_reference_dup, ";");
-       NULL != icoin_reference;
-       icoin_reference = strtok (NULL, ";"))
   {
-    const struct TALER_CoinSpendPrivateKeyP *icoin_priv;
-    struct TALER_CoinSpendPublicKeyP icoin_pub;
-    struct GNUNET_HashCode h_icoin_pub;
-    struct TALER_Amount *iamount;
-    const struct TALER_TESTING_Command *icoin_cmd;
-
-    if (NULL ==
-        (icoin_cmd =
-           TALER_TESTING_interpreter_lookup_command (rls->is,
-                                                     icoin_reference)) )
+    const struct TALER_TESTING_Command *pay_cmd;
+
+    if (NULL == (pay_cmd = TALER_TESTING_interpreter_lookup_command (
+                   rls->is,
+                   rls->pay_reference)))
     {
-      GNUNET_break (0);
-      TALER_LOG_ERROR ("Bad reference `%s'\n",
-                       icoin_reference);
-      TALER_TESTING_interpreter_fail (rls->is);
       GNUNET_CONTAINER_multihashmap_destroy (map);
-      return;
+      TALER_TESTING_FAIL (rls->is);
     }
 
     if (GNUNET_OK !=
-        TALER_TESTING_get_trait_coin_priv (icoin_cmd,
-                                           0,
-                                           &icoin_priv))
+        TALER_TESTING_get_trait_coin_reference (
+          pay_cmd,
+          0,
+          &coin_reference))
     {
-      GNUNET_break (0);
-      TALER_LOG_ERROR ("Command `%s' failed to give coin priv trait\n",
-                       icoin_reference);
-      TALER_TESTING_interpreter_fail (rls->is);
       GNUNET_CONTAINER_multihashmap_destroy (map);
-      return;
+      TALER_TESTING_FAIL (rls->is);
     }
-    GNUNET_CRYPTO_eddsa_key_get_public (&icoin_priv->eddsa_priv,
-                                        &icoin_pub.eddsa_pub);
-    GNUNET_CRYPTO_hash (&icoin_pub,
-                        sizeof (struct TALER_CoinSpendPublicKeyP),
-                        &h_icoin_pub);
-
-    iamount = GNUNET_CONTAINER_multihashmap_get (map,
-                                                 &h_icoin_pub);
-
-    /* Can be NULL: not all coins are involved in refund */
-    if (NULL == iamount)
-      continue;
-    GNUNET_assert (0 <=
-                   TALER_amount_add (&acc,
-                                     &acc,
-                                     iamount));
   }
 
-  GNUNET_free (coin_reference_dup);
-
-  if (NULL !=
-      (increase_cmd
-         = TALER_TESTING_interpreter_lookup_command (rls->is,
-                                                     rls->increase_reference)))
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_get_zero ("EUR",
+                                        &acc));
   {
-    if (GNUNET_OK !=
-        TALER_TESTING_get_trait_string (increase_cmd,
-                                        0,
-                                        &refund_amount))
-      TALER_TESTING_FAIL (rls->is);
+    char *coin_reference_dup;
 
-    if (GNUNET_OK !=
-        TALER_string_to_amount (refund_amount,
-                                &ra))
-      TALER_TESTING_FAIL (rls->is);
+    coin_reference_dup = GNUNET_strdup (coin_reference);
+    for (icoin_reference = strtok (coin_reference_dup, ";");
+         NULL != icoin_reference;
+         icoin_reference = strtok (NULL, ";"))
+    {
+      const struct TALER_CoinSpendPrivateKeyP *icoin_priv;
+      struct TALER_CoinSpendPublicKeyP icoin_pub;
+      struct GNUNET_HashCode h_icoin_pub;
+      const struct TALER_MERCHANT_RefundDetail *idetail;
+      const struct TALER_TESTING_Command *icoin_cmd;
+
+      if (NULL ==
+          (icoin_cmd =
+             TALER_TESTING_interpreter_lookup_command (rls->is,
+                                                       icoin_reference)) )
+      {
+        GNUNET_break (0);
+        TALER_LOG_ERROR ("Bad reference `%s'\n",
+                         icoin_reference);
+        TALER_TESTING_interpreter_fail (rls->is);
+        GNUNET_CONTAINER_multihashmap_destroy (map);
+        return;
+      }
+
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_coin_priv (icoin_cmd,
+                                             0,
+                                             &icoin_priv))
+      {
+        GNUNET_break (0);
+        TALER_LOG_ERROR ("Command `%s' failed to give coin priv trait\n",
+                         icoin_reference);
+        TALER_TESTING_interpreter_fail (rls->is);
+        GNUNET_CONTAINER_multihashmap_destroy (map);
+        return;
+      }
+      GNUNET_CRYPTO_eddsa_key_get_public (&icoin_priv->eddsa_priv,
+                                          &icoin_pub.eddsa_pub);
+      TALER_LOG_DEBUG ("Looking at coin %s\n",
+                       TALER_B2S (&icoin_pub));
+      GNUNET_CRYPTO_hash (&icoin_pub,
+                          sizeof (struct TALER_CoinSpendPublicKeyP),
+                          &h_icoin_pub);
+
+      idetail = GNUNET_CONTAINER_multihashmap_get (map,
+                                                   &h_icoin_pub);
+
+      /* Can be NULL: not all coins are involved in refund */
+      if (NULL == idetail)
+        continue;
+      TALER_LOG_DEBUG ("Found coin %s refund of %s\n",
+                       TALER_B2S (&idetail->coin_pub),
+                       TALER_amount2s (&idetail->refund_amount));
+      GNUNET_assert (0 <=
+                     TALER_amount_add (&acc,
+                                       &acc,
+                                       &idetail->refund_amount));
+    }
+    GNUNET_free (coin_reference_dup);
   }
-  else
+
+
   {
-    GNUNET_assert (NULL != rls->refund_amount);
+    const struct TALER_TESTING_Command *increase_cmd;
 
-    if (GNUNET_OK !=
-        TALER_string_to_amount (rls->refund_amount,
-                                &ra))
-      TALER_TESTING_FAIL (rls->is);
-  }
+    if (NULL !=
+        (increase_cmd
+           = TALER_TESTING_interpreter_lookup_command (rls->is,
+                                                       
rls->increase_reference)))
+    {
+      if (GNUNET_OK !=
+          TALER_TESTING_get_trait_string (increase_cmd,
+                                          0,
+                                          &refund_amount))
+        TALER_TESTING_FAIL (rls->is);
+
+      if (GNUNET_OK !=
+          TALER_string_to_amount (refund_amount,
+                                  &ra))
+        TALER_TESTING_FAIL (rls->is);
+    }
+    else
+    {
+      GNUNET_assert (NULL != rls->refund_amount);
 
-  GNUNET_CONTAINER_multihashmap_iterate (map,
-                                         &hashmap_free,
-                                         NULL);
+      if (GNUNET_OK !=
+          TALER_string_to_amount (rls->refund_amount,
+                                  &ra))
+        TALER_TESTING_FAIL (rls->is);
+    }
+  }
   GNUNET_CONTAINER_multihashmap_destroy (map);
 
   /* Check that what the backend claims to have been refunded
@@ -304,10 +314,14 @@ refund_lookup_cb (void *cls,
   if (0 != TALER_amount_cmp (&acc,
                              &ra))
   {
+    char *a1;
+
+    a1 = TALER_amount_to_string (&ra);
     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                 "Incomplete refund: expected '%s', got '%s'\n",
-                TALER_amount_to_string (&ra),
-                TALER_amount_to_string (&acc));
+                a1,
+                TALER_amount2s (&acc));
+    GNUNET_free (a1);
     TALER_TESTING_interpreter_fail (rls->is);
     return;
   }
diff --git a/src/lib/testing_api_cmd_track_transaction.c 
b/src/lib/testing_api_cmd_track_transaction.c
index e128ce5..c945050 100644
--- a/src/lib/testing_api_cmd_track_transaction.c
+++ b/src/lib/testing_api_cmd_track_transaction.c
@@ -229,12 +229,15 @@ track_transaction_traits (void *cls,
   struct TrackTransactionState *tts = cls;
   struct TALER_WireTransferIdentifierRawP *wtid_ptr;
 
-  if (GNUNET_OK !=
-      GNUNET_STRINGS_string_to_data (
-        tts->wtid_str,
-        strlen (tts->wtid_str),
-        &tts->wtid,
-        sizeof (struct TALER_WireTransferIdentifierRawP)))
+  if (MHD_HTTP_OK != tts->http_status)
+    return GNUNET_SYSERR;
+  if ( (NULL != tts->wtid_str) &&
+       (GNUNET_OK !=
+        GNUNET_STRINGS_string_to_data (tts->wtid_str,
+                                       strlen (tts->wtid_str),
+                                       &tts->wtid,
+                                       sizeof (struct
+                                               
TALER_WireTransferIdentifierRawP))) )
     wtid_ptr = NULL;
   else
     wtid_ptr = &tts->wtid;

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

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