gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] 03/03: implement #5299


From: gnunet
Subject: [taler-merchant] 03/03: implement #5299
Date: Fri, 10 Apr 2020 20:15:03 +0200

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

grothoff pushed a commit to branch master
in repository merchant.

commit 0b945df357bf820540b67b412d47aa9073797267
Author: Christian Grothoff <address@hidden>
AuthorDate: Fri Apr 10 20:14:57 2020 +0200

    implement #5299
---
 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                | 579 ++++++++-----------
 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 +-
 29 files changed, 1424 insertions(+), 711 deletions(-)

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 ccd868b..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 ()
   };
 
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]