gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] 67/277: work on /pay API revision


From: gnunet
Subject: [taler-merchant] 67/277: work on /pay API revision
Date: Sun, 05 Jul 2020 20:49:40 +0200

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

grothoff pushed a commit to branch master
in repository merchant.

commit ce97f3d2e12b4da31eb0f611d401f56ce8052de5
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Wed Apr 29 22:02:54 2020 +0200

    work on /pay API revision
---
 src/backend/Makefile.am                            |    4 +-
 src/backend/taler-merchant-httpd.c                 |    4 +
 .../taler-merchant-httpd_post-orders-ID-pay.c      | 2112 ++++++++------------
 .../taler-merchant-httpd_post-orders-ID-pay.h      |   27 +-
 src/include/taler_merchant_service.h               |  504 ++---
 src/include/taler_merchantdb_plugin.h              |  189 +-
 6 files changed, 1245 insertions(+), 1595 deletions(-)

diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index 316f6e3..4f74af4 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -52,7 +52,9 @@ taler_merchant_httpd_SOURCES = \
   taler-merchant-httpd_private-post-orders.c \
     taler-merchant-httpd_private-post-orders.h \
   taler-merchant-httpd_post-orders-ID-claim.c \
-    taler-merchant-httpd_post-orders-ID-claim.h
+    taler-merchant-httpd_post-orders-ID-claim.h \
+  taler-merchant-httpd_post-orders-ID-pay.c \
+    taler-merchant-httpd_post-orders-ID-pay.h
 
 DEAD = \
   taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \
diff --git a/src/backend/taler-merchant-httpd.c 
b/src/backend/taler-merchant-httpd.c
index 7d255b9..0149b8b 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -513,6 +513,8 @@ handle_mhd_completion_callback (void *cls,
   GNUNET_free_non_null (hc->infix);
   if (NULL != hc->request_body)
     json_decref (hc->request_body);
+  if (NULL != hc->instance)
+    TMH_instance_decref (hc->instance);
   GNUNET_free (hc);
   *con_cls = NULL;
 }
@@ -972,6 +974,8 @@ url_handler (void *cls,
       /* use 'default' */
       hc->instance = TMH_lookup_instance (NULL);
     }
+    if (NULL != hc->instance)
+      hc->instance->rc++;
   }
 
   {
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index 7a1b7fd..004b5e4 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -18,22 +18,19 @@
 */
 
 /**
- * @file backend/taler-merchant-httpd_pay.c
- * @brief handling of /pay requests
+ * @file backend/taler-merchant-httpd_post-orders-ID-pay.c
+ * @brief handling of POST /orders/$ID/pay requests
  * @author Marcello Stanisci
  * @author Christian Grothoff
  * @author Florian Dold
  */
 #include "platform.h"
-#include <jansson.h>
-#include <gnunet/gnunet_util_lib.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_auditors.h"
 #include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_refund.h"
+#include "taler-merchant-httpd_post-orders-ID-pay.h"
 
 
 /**
@@ -48,12 +45,12 @@
 #define MAX_RETRIES 5
 
 /**
- * Information we keep for an individual call to the /pay handler.
+ * Information we keep for an individual call to the pay handler.
  */
 struct PayContext;
 
 /**
- * Information kept during a /pay request for each coin.
+ * Information kept during a pay request for each coin.
  */
 struct DepositConfirmation
 {
@@ -75,9 +72,9 @@ struct DepositConfirmation
   char *exchange_url;
 
   /**
-   * Denomination of this coin.
+   * Hash of the denomination of this coin.
    */
-  struct TALER_DenominationPublicKey denom;
+  struct GNUNET_HashCode h_denom;
 
   /**
    * Amount this coin contributes to the total purchase price.
@@ -122,14 +119,9 @@ struct DepositConfirmation
   unsigned int index;
 
   /**
-   * #GNUNET_YES if we found this coin in the database.
-   */
-  int found_in_db;
-
-  /**
-   * #GNUNET_YES if this coin was refunded.
+   * true if we found this coin in the database.
    */
-  int refunded;
+  bool found_in_db;
 
 };
 
@@ -140,12 +132,6 @@ struct DepositConfirmation
 struct PayContext
 {
 
-  /**
-   * This field MUST be first for handle_mhd_completion_callback() to work
-   * when it treats this struct as a `struct TM_HandlerContext`.
-   */
-  struct TM_HandlerContext hc;
-
   /**
    * Stored in a DLL.
    */
@@ -167,21 +153,15 @@ struct PayContext
   struct MHD_Connection *connection;
 
   /**
-   * Instance of the payment's instance (in JSON format)
+   * Details about the client's request.
    */
-  struct MerchantInstance *mi;
+  struct TMH_HandlerContext *hc;
 
   /**
    * What wire method (of the @e mi) was selected by the wallet?
    * Set in #parse_pay().
    */
-  struct WireMethod *wm;
-
-  /**
-   * Proposal data for the proposal that is being
-   * paid for in this context.
-   */
-  json_t *contract_terms;
+  struct TMH_WireMethod *wm;
 
   /**
    * Task called when the (suspended) processing for
@@ -195,12 +175,6 @@ struct PayContext
    */
   struct MHD_Response *response;
 
-  /**
-   * Handle to the exchange that we are doing the payment with.
-   * (initially NULL while @e fo is trying to find a exchange).
-   */
-  struct TALER_EXCHANGE_Handle *mh;
-
   /**
    * Handle for operation to lookup /keys (and auditors) from
    * the exchange used for this transaction; NULL if no operation is
@@ -229,11 +203,6 @@ struct PayContext
    */
   char *order_id;
 
-  /**
-   * Fulfillment URL from @e contract_terms.
-   */
-  char *fulfillment_url;
-
   /**
    * Hashed proposal.
    */
@@ -375,11 +344,6 @@ struct PayContext
    */
   int tried_force_keys;
 
-  /**
-   * Which operational mode is the /pay request made in?
-   */
-  enum { PC_MODE_PAY, PC_MODE_ABORT_REFUND } mode;
-
 };
 
 
@@ -495,109 +459,26 @@ resume_pay_with_error (struct PayContext *pc,
 }
 
 
-/**
- * Generate a response that indicates payment success.
- *
- * @param pc payment context
- */
-static void
-generate_success_response (struct PayContext *pc)
-{
-  json_t *refunds;
-  struct GNUNET_CRYPTO_EddsaSignature sig;
-
-  /* Check for applicable refunds */
-  {
-    enum TALER_ErrorCode ec;
-    const char *errmsg;
-
-    refunds = TM_get_refund_json (pc->mi,
-                                  &pc->h_contract_terms,
-                                  &ec,
-                                  &errmsg);
-    /* We would get an EMPTY array back on success if there
-       are no refunds, but not NULL. So NULL is always an error. */
-    if (NULL == refunds)
-    {
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             ec,
-                             errmsg);
-      return;
-    }
-  }
-
-  /* Sign on our end (as the payment did go through, even if it may
-     have been refunded already) */
-  {
-    struct PaymentResponsePS mr = {
-      .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK),
-      .purpose.size = htonl (sizeof (mr)),
-      .h_contract_terms = pc->h_contract_terms
-    };
-
-    GNUNET_CRYPTO_eddsa_sign (&pc->mi->privkey.eddsa_priv,
-                              &mr,
-                              &sig);
-  }
-
-  /* Build the response */
-  {
-    json_t *resp;
-
-    resp = json_pack ("{s:O, s:o, s:o, s:o}",
-                      "contract_terms",
-                      pc->contract_terms,
-                      "sig",
-                      GNUNET_JSON_from_data_auto (&sig),
-                      "h_contract_terms",
-                      GNUNET_JSON_from_data (&pc->h_contract_terms,
-                                             sizeof (struct GNUNET_HashCode)),
-                      "refund_permissions",
-                      refunds);
-    if (NULL == resp)
-    {
-      GNUNET_break (0);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_JSON_ALLOCATION_FAILURE,
-                             "could not build final response");
-      return;
-    }
-    resume_pay_with_response (pc,
-                              MHD_HTTP_OK,
-                              TALER_MHD_make_json (resp));
-    json_decref (resp);
-  }
-}
-
-
 /**
  * Custom cleanup routine for a `struct PayContext`.
  *
- * @param hc the `struct PayContext` to clean up.
+ * @param cls the `struct PayContext` to clean up.
  */
 static void
-pay_context_cleanup (struct TM_HandlerContext *hc)
+pay_context_cleanup (void *cls)
 {
-  struct PayContext *pc = (struct PayContext *) hc;
+  struct PayContext *pc = cls;
 
   if (NULL != pc->timeout_task)
   {
     GNUNET_SCHEDULER_cancel (pc->timeout_task);
     pc->timeout_task = NULL;
   }
-  TALER_MHD_parse_post_cleanup_callback (pc->json_parse_context);
   abort_deposit (pc);
   for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
 
-    if (NULL != dc->denom.rsa_public_key)
-    {
-      GNUNET_CRYPTO_rsa_public_key_free (dc->denom.rsa_public_key);
-      dc->denom.rsa_public_key = NULL;
-    }
     if (NULL != dc->ub_sig.rsa_signature)
     {
       GNUNET_CRYPTO_rsa_signature_free (dc->ub_sig.rsa_signature);
@@ -616,14 +497,8 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
     MHD_destroy_response (pc->response);
     pc->response = NULL;
   }
-  if (NULL != pc->contract_terms)
-  {
-    json_decref (pc->contract_terms);
-    pc->contract_terms = NULL;
-  }
   GNUNET_free_non_null (pc->order_id);
   GNUNET_free_non_null (pc->session_id);
-  GNUNET_free_non_null (pc->fulfillment_url);
   GNUNET_CONTAINER_DLL_remove (pc_head,
                                pc_tail,
                                pc);
@@ -632,421 +507,154 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
 
 
 /**
- * Check whether the amount paid is sufficient to cover
- * the contract.
+ * Find the exchange we need to talk to for the next
+ * pending deposit permission.
  *
- * @param pc payment context to check
- * @return #GNUNET_OK if the payment is sufficient, #GNUNET_SYSERR if it is
- *         insufficient
+ * @param pc payment context we are processing
  */
-static int
-check_payment_sufficient (struct PayContext *pc)
-{
-  struct TALER_Amount acc_fee;
-  struct TALER_Amount acc_amount;
-  struct TALER_Amount final_amount;
-  struct TALER_Amount wire_fee_delta;
-  struct TALER_Amount wire_fee_customer_contribution;
-  struct TALER_Amount total_wire_fee;
-  struct TALER_Amount total_needed;
+static void
+find_next_exchange (struct PayContext *pc);
 
-  if (0 == pc->coins_cnt)
-  {
-    GNUNET_break_op (0);
-    resume_pay_with_error (pc,
-                           MHD_HTTP_BAD_REQUEST,
-                           TALER_EC_PAY_PAYMENT_INSUFFICIENT,
-                           "insufficient funds (no coins!)");
-    return GNUNET_SYSERR;
-  }
 
-  acc_fee = pc->dc[0].deposit_fee;
-  total_wire_fee = pc->dc[0].wire_fee;
-  acc_amount = pc->dc[0].amount_with_fee;
+/**
+ * Begin of the DB transaction.  If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
+ *
+ * @param pc payment context to transact
+ */
+static void
+begin_transaction (struct PayContext *pc);
 
-  /**
-   * This loops calculates what are the deposit fee / total
-   * amount with fee / and wire fee, for all the coins.
-   */
-  for (unsigned int i = 1; i<pc->coins_cnt; i++)
+
+/**
+ * Callback to handle a deposit permission's response.
+ *
+ * @param cls a `struct DepositConfirmation` (i.e. a pointer
+ *   into the global array of confirmations and an index for this call
+ *   in that array). That way, the last executed callback can detect
+ *   that no other confirmations are on the way, and can pack a response
+ *   for the wallet
+ * @param hr HTTP response code details
+ * @param exchange_sig signature from the exchange over the deposit 
confirmation
+ * @param exchange_pub_key which key did the exchange use to create the @a 
exchange_sig
+ */
+static void
+deposit_cb (void *cls,
+            const struct TALER_EXCHANGE_HttpResponse *hr,
+            const struct TALER_ExchangeSignatureP *exchange_sig,
+            const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+  struct DepositConfirmation *dc = cls;
+  struct PayContext *pc = dc->pc;
+  enum GNUNET_DB_QueryStatus qs;
+
+  dc->dh = NULL;
+  GNUNET_assert (GNUNET_YES == pc->suspended);
+  pc->pending_at_ce--;
+  if (MHD_HTTP_OK != hr->http_status)
   {
-    struct DepositConfirmation *dc = &pc->dc[i];
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Deposit operation failed with HTTP code %u/%d\n",
+                hr->http_status,
+                (int) hr->ec);
+    /* Transaction failed; stop all other ongoing deposits */
+    abort_deposit (pc);
 
-    GNUNET_assert (GNUNET_YES == dc->found_in_db);
-    if ( (0 >
-          TALER_amount_add (&acc_fee,
-                            &dc->deposit_fee,
-                            &acc_fee)) ||
-         (0 >
-          TALER_amount_add (&acc_amount,
-                            &dc->amount_with_fee,
-                            &acc_amount)) )
+    if (5 == hr->http_status / 100)
     {
-      GNUNET_break (0);
-      /* Overflow in these amounts? Very strange. */
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_PAY_AMOUNT_OVERFLOW,
-                             "Overflow adding up amounts");
+      /* internal server error at exchange */
+      resume_pay_with_response (pc,
+                                MHD_HTTP_SERVICE_UNAVAILABLE,
+                                TALER_MHD_make_json_pack (
+                                  "{s:s, s:I, s:I, s:I}",
+                                  "hint",
+                                  "exchange had an internal server error",
+                                  "code",
+                                  (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
+                                  "exchange_code",
+                                  (json_int_t) hr->ec,
+                                  "exchange_http_status",
+                                  (json_int_t) hr->http_status));
     }
-    if (1 ==
-        TALER_amount_cmp (&dc->deposit_fee,
-                          &dc->amount_with_fee))
+    else if (NULL == hr->reply)
     {
-      GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_BAD_REQUEST,
-                             TALER_EC_PAY_FEES_EXCEED_PAYMENT,
-                             "Deposit fees exceed coin's contribution");
-      return GNUNET_SYSERR;
+      /* We can't do anything meaningful here, the exchange did something 
wrong */
+      resume_pay_with_response (pc,
+                                MHD_HTTP_FAILED_DEPENDENCY,
+                                TALER_MHD_make_json_pack (
+                                  "{s:s, s:I, s:I, s:I}",
+                                  "hint",
+                                  "exchange failed, response body not even in 
JSON",
+                                  "code",
+                                  (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
+                                  "exchange_code",
+                                  (json_int_t) hr->ec,
+                                  "exchange_http_status",
+                                  (json_int_t) hr->http_status));
     }
-
-    /* If exchange differs, add wire fee */
+    else
     {
-      int new_exchange = GNUNET_YES;
-
-      for (unsigned int j = 0; j<i; j++)
-        if (0 == strcasecmp (dc->exchange_url,
-                             pc->dc[j].exchange_url))
-        {
-          new_exchange = GNUNET_NO;
-          break;
-        }
-      if (GNUNET_YES == new_exchange)
-      {
-        if (GNUNET_OK !=
-            TALER_amount_cmp_currency (&total_wire_fee,
-                                       &dc->wire_fee))
-        {
-          GNUNET_break_op (0);
-          resume_pay_with_error (pc,
-                                 MHD_HTTP_PRECONDITION_FAILED,
-                                 TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
-                                 "exchange wire in different currency");
-          return GNUNET_SYSERR;
-        }
-        if (0 >
-            TALER_amount_add (&total_wire_fee,
-                              &total_wire_fee,
-                              &dc->wire_fee))
-        {
-          GNUNET_break (0);
-          resume_pay_with_error (pc,
-                                 MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                 
TALER_EC_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
-                                 "could not add exchange wire fee to total");
-          return GNUNET_SYSERR;
-        }
-      }
+      /* Forward error, adding the "coin_pub" for which the
+         error was being generated */
+      if (TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS == hr->ec)
+        resume_pay_with_response (
+          pc,
+          MHD_HTTP_CONFLICT,
+          TALER_MHD_make_json_pack ("{s:s, s:I, s:I, s:I, s:o, s:O}",
+                                    "hint",
+                                    "exchange failed on deposit of a coin",
+                                    "code",
+                                    (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
+                                    "exchange_code",
+                                    (json_int_t) hr->ec,
+                                    "exchange_http_status",
+                                    (json_int_t) hr->http_status,
+                                    "coin_pub",
+                                    GNUNET_JSON_from_data_auto (&dc->coin_pub),
+                                    "exchange_reply",
+                                    hr->reply));
+      else
+        resume_pay_with_response (
+          pc,
+          MHD_HTTP_FAILED_DEPENDENCY,
+          TALER_MHD_make_json_pack ("{s:s, s:I, s:I, s:I, s:o, s:O}",
+                                    "hint",
+                                    "exchange failed on deposit of a coin",
+                                    "code",
+                                    (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
+                                    "exchange_code",
+                                    (json_int_t) hr->ec,
+                                    "exchange_http_status",
+                                    (json_int_t) hr->http_status,
+                                    "coin_pub",
+                                    GNUNET_JSON_from_data_auto (&dc->coin_pub),
+                                    "exchange_reply",
+                                    hr->reply));
     }
+    return;
   }
 
+  /* store result to DB */
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Amount received from wallet: %s\n",
-              TALER_amount2s (&acc_amount));
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Deposit fee for all coins: %s\n",
-              TALER_amount2s (&acc_fee));
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Total wire fee: %s\n",
-              TALER_amount2s (&total_wire_fee));
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Max wire fee: %s\n",
-              TALER_amount2s (&pc->max_wire_fee));
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Deposit fee limit for merchant: %s\n",
-              TALER_amount2s (&pc->max_fee));
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Total refunded amount: %s\n",
-              TALER_amount2s (&pc->total_refunded));
-
-  /* Now compare exchange wire fee compared to
-   * what we are willing to pay */
-  if (GNUNET_YES !=
-      TALER_amount_cmp_currency (&total_wire_fee,
-                                 &pc->max_wire_fee))
-  {
-    resume_pay_with_error (pc,
-                           MHD_HTTP_PRECONDITION_FAILED,
-                           TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
-                           "exchange wire does not match our currency");
-    return GNUNET_SYSERR;
-  }
-
-  switch (TALER_amount_subtract (&wire_fee_delta,
-                                 &total_wire_fee,
-                                 &pc->max_wire_fee))
-  {
-  case TALER_AAR_RESULT_POSITIVE:
-    /* Actual wire fee is indeed higher than our maximum,
-       compute how much the customer is expected to cover!  */
-    TALER_amount_divide (&wire_fee_customer_contribution,
-                         &wire_fee_delta,
-                         pc->wire_fee_amortization);
-    break;
-  case TALER_AAR_RESULT_ZERO:
-  case TALER_AAR_INVALID_NEGATIVE_RESULT:
-    /* Wire fee threshold is still above the wire fee amount.
-       Customer is not going to contribute on this.  */
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_get_zero (total_wire_fee.currency,
-                                          &wire_fee_customer_contribution));
-    break;
-  default:
-    GNUNET_assert (0);
-  }
-
-  /* add wire fee contribution to the total fees */
-  if (0 >
-      TALER_amount_add (&acc_fee,
-                        &acc_fee,
-                        &wire_fee_customer_contribution))
-  {
-    GNUNET_break (0);
-    resume_pay_with_error (pc,
-                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                           TALER_EC_PAY_AMOUNT_OVERFLOW,
-                           "Overflow adding up amounts");
-    return GNUNET_SYSERR;
-  }
-  if (-1 == TALER_amount_cmp (&pc->max_fee,
-                              &acc_fee))
-  {
-    /**
-     * Sum of fees of *all* the different exchanges of all the coins are
-     * higher than the fixed limit that the merchant is willing to pay.  The
-     * difference must be paid by the customer.
-     *///
-    struct TALER_Amount excess_fee;
-
-    /* compute fee amount to be covered by customer */
-    GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
-                   TALER_amount_subtract (&excess_fee,
-                                          &acc_fee,
-                                          &pc->max_fee));
-    /* add that to the total */
-    if (0 >
-        TALER_amount_add (&total_needed,
-                          &excess_fee,
-                          &pc->amount))
-    {
-      GNUNET_break (0);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_PAY_AMOUNT_OVERFLOW,
-                             "Overflow adding up amounts");
-      return GNUNET_SYSERR;
-    }
-  }
-  else
-  {
-    /* Fees are fully covered by the merchant, all we require
-       is that the total payment is not below the contract's amount */
-    total_needed = pc->amount;
-  }
-
-  /* Do not count refunds towards the payment */
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Subtracting total refunds from paid amount: %s\n",
-              TALER_amount2s (&pc->total_refunded));
-  if (0 >
-      TALER_amount_subtract (&final_amount,
-                             &acc_amount,
-                             &pc->total_refunded))
-  {
-    GNUNET_break (0);
-    resume_pay_with_error (pc,
-                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                           TALER_EC_PAY_REFUNDS_EXCEED_PAYMENTS,
-                           "refunded amount exceeds total payments");
-    return GNUNET_SYSERR;
-  }
-
-  if (-1 == TALER_amount_cmp (&final_amount,
-                              &total_needed))
-  {
-    /* acc_amount < total_needed */
-    if (-1 < TALER_amount_cmp (&acc_amount,
-                               &total_needed))
-    {
-      resume_pay_with_error (pc,
-                             MHD_HTTP_PAYMENT_REQUIRED,
-                             TALER_EC_PAY_REFUNDED,
-                             "contract not paid up due to refunds");
-    }
-    else if (-1 < TALER_amount_cmp (&acc_amount,
-                                    &pc->amount))
-    {
-      GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             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)");
-    }
-    else
-    {
-      GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_NOT_ACCEPTABLE,
-                             TALER_EC_PAY_PAYMENT_INSUFFICIENT,
-                             "payment insufficient");
-
-    }
-    return GNUNET_SYSERR;
-  }
-
-
-  return GNUNET_OK;
-}
-
-
-/**
- * Find the exchange we need to talk to for the next
- * pending deposit permission.
- *
- * @param pc payment context we are processing
- */
-static void
-find_next_exchange (struct PayContext *pc);
-
-
-/**
- * Begin of the DB transaction.  If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
- *
- * @param pc payment context to transact
- */
-static void
-begin_transaction (struct PayContext *pc);
-
-
-/**
- * Callback to handle a deposit permission's response.
- *
- * @param cls a `struct DepositConfirmation` (i.e. a pointer
- *   into the global array of confirmations and an index for this call
- *   in that array). That way, the last executed callback can detect
- *   that no other confirmations are on the way, and can pack a response
- *   for the wallet
- * @param hr HTTP response code details
- * @param exchange_sig signature from the exchange over the deposit 
confirmation
- * @param sign_key which key did the exchange use to sign the @a proof
- */
-static void
-deposit_cb (void *cls,
-            const struct TALER_EXCHANGE_HttpResponse *hr,
-            const struct TALER_ExchangeSignatureP *exchange_sig,
-            const struct TALER_ExchangePublicKeyP *sign_key)
-{
-  struct DepositConfirmation *dc = cls;
-  struct PayContext *pc = dc->pc;
-  enum GNUNET_DB_QueryStatus qs;
-
-  dc->dh = NULL;
-  GNUNET_assert (GNUNET_YES == pc->suspended);
-  pc->pending_at_ce--;
-  if (MHD_HTTP_OK != hr->http_status)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                "Deposit operation failed with HTTP code %u/%d\n",
-                hr->http_status,
-                (int) hr->ec);
-    /* Transaction failed; stop all other ongoing deposits */
-    abort_deposit (pc);
-
-    if (5 == hr->http_status / 100)
-    {
-      /* internal server error at exchange */
-      resume_pay_with_response (pc,
-                                MHD_HTTP_SERVICE_UNAVAILABLE,
-                                TALER_MHD_make_json_pack (
-                                  "{s:s, s:I, s:I, s:I}",
-                                  "hint",
-                                  "exchange had an internal server error",
-                                  "code",
-                                  (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                  "exchange_code",
-                                  (json_int_t) hr->ec,
-                                  "exchange_http_status",
-                                  (json_int_t) hr->http_status));
-    }
-    else if (NULL == hr->reply)
-    {
-      /* We can't do anything meaningful here, the exchange did something 
wrong */
-      resume_pay_with_response (pc,
-                                MHD_HTTP_FAILED_DEPENDENCY,
-                                TALER_MHD_make_json_pack (
-                                  "{s:s, s:I, s:I, s:I}",
-                                  "hint",
-                                  "exchange failed, response body not even in 
JSON",
-                                  "code",
-                                  (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                  "exchange_code",
-                                  (json_int_t) hr->ec,
-                                  "exchange_http_status",
-                                  (json_int_t) hr->http_status));
-    }
-    else
-    {
-      /* Forward error, adding the "coin_pub" for which the
-         error was being generated */
-      if (TALER_EC_DEPOSIT_INSUFFICIENT_FUNDS == hr->ec)
-        resume_pay_with_response (
-          pc,
-          MHD_HTTP_CONFLICT,
-          TALER_MHD_make_json_pack ("{s:s, s:I, s:I, s:I, s:o, s:O}",
-                                    "hint",
-                                    "exchange failed on deposit of a coin",
-                                    "code",
-                                    (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                    "exchange_code",
-                                    (json_int_t) hr->ec,
-                                    "exchange_http_status",
-                                    (json_int_t) hr->http_status,
-                                    "coin_pub",
-                                    GNUNET_JSON_from_data_auto (&dc->coin_pub),
-                                    "exchange_reply",
-                                    hr->reply));
-      else
-        resume_pay_with_response (
-          pc,
-          MHD_HTTP_FAILED_DEPENDENCY,
-          TALER_MHD_make_json_pack ("{s:s, s:I, s:I, s:I, s:o, s:O}",
-                                    "hint",
-                                    "exchange failed on deposit of a coin",
-                                    "code",
-                                    (json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                    "exchange_code",
-                                    (json_int_t) hr->ec,
-                                    "exchange_http_status",
-                                    (json_int_t) hr->http_status,
-                                    "coin_pub",
-                                    GNUNET_JSON_from_data_auto (&dc->coin_pub),
-                                    "exchange_reply",
-                                    hr->reply));
-    }
-    return;
-  }
-  /* store result to DB */
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Storing successful payment for h_contract_terms `%s' and 
merchant `%s'\n",
+              "Storing successful payment %s (%s) at instance `%s'\n",
+              pc->hc->infix,
               GNUNET_h2s (&pc->h_contract_terms),
-              TALER_B2S (&pc->mi->pubkey));
+              pc->hc->instance->settings.id);
   /* NOTE: not run in any transaction block, simply as a
      transaction by itself! */
-  db->preflight (db->cls);
-  qs = db->store_deposit (db->cls,
-                          &pc->h_contract_terms,
-                          &pc->mi->pubkey,
-                          &dc->coin_pub,
-                          dc->exchange_url,
-                          &dc->amount_with_fee,
-                          &dc->deposit_fee,
-                          &dc->refund_fee,
-                          &dc->wire_fee,
-                          sign_key,
-                          hr->reply);
+  TMH_db->preflight (TMH_db->cls);
+  qs = TMH_db->insert_deposit (TMH_db->cls,
+                               pc->hc->instance->settings.id,
+                               &pc->h_contract_terms,
+                               &dc->coin_pub,
+                               dc->exchange_url,
+                               &dc->amount_with_fee,
+                               &dc->deposit_fee,
+                               &dc->refund_fee,
+                               &dc->wire_fee,
+                               exchange_sig,
+                               exchange_pub);
   if (0 > qs)
   {
     /* Special report if retries insufficient */
@@ -1065,7 +673,7 @@ deposit_cb (void *cls,
                            "Merchant database error");
     return;
   }
-  dc->found_in_db = GNUNET_YES;
+  dc->found_in_db = true; /* well, at least NOW it'd be true ;-) */
   pc->pending--;
 
   if (0 != pc->pending_at_ce)
@@ -1079,8 +687,8 @@ deposit_cb (void *cls,
  *
  * @param cls the `struct PayContext`
  * @param hr HTTP response details
- * @param mh NULL if exchange was not found to be acceptable
- * @param wire_fee current applicable fee for dealing with @a mh,
+ * @param exchange_handle NULL if exchange was not found to be acceptable
+ * @param wire_fee current applicable fee for dealing with @a exchange_handle,
  *        NULL if not available
  * @param exchange_trusted #GNUNET_YES if this exchange is
  *        trusted by config
@@ -1088,11 +696,12 @@ deposit_cb (void *cls,
 static void
 process_pay_with_exchange (void *cls,
                            const struct TALER_EXCHANGE_HttpResponse *hr,
-                           struct TALER_EXCHANGE_Handle *mh,
+                           struct TALER_EXCHANGE_Handle *exchange_handle,
                            const struct TALER_Amount *wire_fee,
                            int exchange_trusted)
 {
   struct PayContext *pc = cls;
+  struct TMH_HandlerContext *hc = pc->hc;
   const struct TALER_EXCHANGE_Keys *keys;
 
   pc->fo = NULL;
@@ -1120,8 +729,7 @@ process_pay_with_exchange (void *cls,
         hr->reply));
     return;
   }
-  pc->mh = mh;
-  keys = TALER_EXCHANGE_get_keys (mh);
+  keys = TALER_EXCHANGE_get_keys (exchange_handle);
   if (NULL == keys)
   {
     GNUNET_break (0); /* should not be possible if HTTP status is #MHD_HTTP_OK 
*/
@@ -1132,12 +740,6 @@ process_pay_with_exchange (void *cls,
     return;
   }
 
-  GNUNET_log (
-    GNUNET_ERROR_TYPE_DEBUG,
-    "Found transaction data for proposal `%s' of merchant `%s', initiating 
deposits\n",
-    GNUNET_h2s (&pc->h_contract_terms),
-    TALER_B2S (&pc->mi->pubkey));
-
   /* Initiate /deposit operation for all coins of
      the current exchange (!) */
   GNUNET_assert (0 == pc->pending_at_ce);
@@ -1146,22 +748,21 @@ process_pay_with_exchange (void *cls,
     struct DepositConfirmation *dc = &pc->dc[i];
     const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
     enum TALER_ErrorCode ec;
-    unsigned int hc;
+    unsigned int http_status;
 
     if (NULL != dc->dh)
       continue; /* we were here before (can happen due to
                    tried_force_keys logic), don't go again */
-    if (GNUNET_YES == dc->found_in_db)
+    if (dc->found_in_db)
       continue;
     if (0 != strcmp (dc->exchange_url,
                      pc->current_exchange))
       continue;
-    denom_details = TALER_EXCHANGE_get_denomination_key (keys,
-                                                         &dc->denom);
+    denom_details
+      = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
+                                                     &dc->h_denom);
     if (NULL == denom_details)
     {
-      struct GNUNET_HashCode h_denom;
-
       if (! pc->tried_force_keys)
       {
         /* let's try *forcing* a re-download of /keys from the exchange.
@@ -1176,8 +777,6 @@ process_pay_with_exchange (void *cls,
           return;
       }
       /* Forcing failed or we already did it, give up */
-      GNUNET_CRYPTO_rsa_public_key_hash (dc->denom.rsa_public_key,
-                                         &h_denom);
       resume_pay_with_response (
         pc,
         MHD_HTTP_FAILED_DEPENDENCY,
@@ -1185,49 +784,47 @@ process_pay_with_exchange (void *cls,
           "{s:s, s:I, s:o, s:o}",
           "hint", "coin's denomination not found",
           "code", TALER_EC_PAY_DENOMINATION_KEY_NOT_FOUND,
-          "h_denom_pub", GNUNET_JSON_from_data_auto (&h_denom),
-          "exchange_keys", TALER_EXCHANGE_get_keys_raw (mh)));
+          "h_denom_pub", GNUNET_JSON_from_data_auto (&dc->h_denom),
+          "exchange_keys", TALER_EXCHANGE_get_keys_raw (exchange_handle)));
       return;
     }
     if (GNUNET_OK !=
-        TMH_AUDITORS_check_dk (mh,
+        TMH_AUDITORS_check_dk (exchange_handle,
                                denom_details,
                                exchange_trusted,
-                               &hc,
+                               &http_status,
                                &ec))
     {
       resume_pay_with_response (
         pc,
-        hc,
-        TALER_MHD_make_json_pack ("{s:s, s:I, s:o}",
-                                  "hint", "denomination not accepted",
-                                  "code", (json_int_t) ec,
-                                  "h_denom_pub", GNUNET_JSON_from_data_auto (
-                                    &denom_details->h_key)));
+        http_status,
+        TALER_MHD_make_json_pack (
+          "{s:s, s:I, s:o}",
+          "hint",
+          "denomination not accepted",
+          "code",
+          (json_int_t) ec,
+          "h_denom_pub",
+          GNUNET_JSON_from_data_auto (&denom_details->h_key)));
       return;
     }
 
     dc->deposit_fee = denom_details->fee_deposit;
     dc->refund_fee = denom_details->fee_refund;
     dc->wire_fee = *wire_fee;
-
     GNUNET_assert (NULL != pc->wm);
     GNUNET_assert (NULL != pc->wm->j_wire);
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Timing for this payment, wire_deadline: %llu, 
refund_deadline: %llu\n",
-                (unsigned long long) pc->wire_transfer_deadline.abs_value_us,
-                (unsigned long long) pc->refund_deadline.abs_value_us);
-    db->preflight (db->cls);
-    dc->dh = TALER_EXCHANGE_deposit (mh,
+    TMH_db->preflight (TMH_db->cls);
+    dc->dh = TALER_EXCHANGE_deposit (exchange_handle,
                                      &dc->amount_with_fee,
                                      pc->wire_transfer_deadline,
                                      pc->wm->j_wire,
                                      &pc->h_contract_terms,
                                      &dc->coin_pub,
                                      &dc->ub_sig,
-                                     &dc->denom,
+                                     &denom_details->key,
                                      pc->timestamp,
-                                     &pc->mi->pubkey,
+                                     &hc->instance->merchant_pub,
                                      pc->refund_deadline,
                                      &dc->coin_sig,
                                      &deposit_cb,
@@ -1264,13 +861,14 @@ process_pay_with_exchange (void *cls,
 static void
 find_next_exchange (struct PayContext *pc)
 {
+  GNUNET_assert (0 == pc->pending_at_ce);
   for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
 
-    if (GNUNET_YES != dc->found_in_db)
+    if (! dc->found_in_db)
     {
-      db->preflight (db->cls);
+      TMH_db->preflight (TMH_db->cls);
       pc->current_exchange = dc->exchange_url;
       pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
                                             pc->wm->wire_method,
@@ -1290,39 +888,14 @@ find_next_exchange (struct PayContext *pc)
     }
   }
   pc->current_exchange = NULL;
-  db->preflight (db->cls);
+  TMH_db->preflight (TMH_db->cls);
   /* We are done with all the HTTP requests, go back and try
      the 'big' database transaction! (It should work now!) */
+  GNUNET_assert (0 == pc->pending);
   begin_transaction (pc);
 }
 
 
-/**
- * Handle a timeout for the processing of the pay request.
- *
- * @param cls our `struct PayContext`
- */
-static void
-handle_pay_timeout (void *cls)
-{
-  struct PayContext *pc = cls;
-
-  pc->timeout_task = NULL;
-  GNUNET_assert (GNUNET_YES == pc->suspended);
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Resuming /pay with error after timeout\n");
-  if (NULL != pc->fo)
-  {
-    TMH_EXCHANGES_find_exchange_cancel (pc->fo);
-    pc->fo = NULL;
-  }
-  resume_pay_with_error (pc,
-                         MHD_HTTP_REQUEST_TIMEOUT,
-                         TALER_EC_PAY_EXCHANGE_TIMEOUT,
-                         "likely the exchange did not reply quickly enough");
-}
-
-
 /**
  * Function called with information about a coin that was deposited.
  *
@@ -1338,461 +911,347 @@ handle_pay_timeout (void *cls)
  */
 static void
 check_coin_paid (void *cls,
-                 const struct GNUNET_HashCode *h_contract_terms,
                  const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                 const char *exchange_url,
                  const struct TALER_Amount *amount_with_fee,
                  const struct TALER_Amount *deposit_fee,
                  const struct TALER_Amount *refund_fee,
-                 const struct TALER_Amount *wire_fee,
-                 const json_t *exchange_proof)
+                 const struct TALER_Amount *wire_fee)
 {
   struct PayContext *pc = cls;
 
-  if (0 != GNUNET_memcmp (&pc->h_contract_terms,
-                          h_contract_terms))
-  {
-    GNUNET_break (0);
-    return;
-  }
   for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
 
-    if (GNUNET_YES == dc->found_in_db)
-      continue; /* processed earlier */
-
+    if (dc->found_in_db)
+      continue; /* processed earlier, skip "expensive" memcmp() */
     /* Get matching coin from results*/
     if ( (0 != GNUNET_memcmp (coin_pub,
                               &dc->coin_pub)) ||
          (0 != TALER_amount_cmp (amount_with_fee,
                                  &dc->amount_with_fee)) )
-      continue;
+      continue; /* does not match, skip */
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Coin (%s) already found in our DB.\n",
-                TALER_b2s (coin_pub,
-                           sizeof (*coin_pub)));
-    if (0 >
-        TALER_amount_add (&pc->total_paid,
-                          &pc->total_paid,
-                          amount_with_fee))
-    {
-      /* We accepted this coin for payment on this contract before,
-         and now we can't even add the amount!? */
-      GNUNET_break (0);
-      continue;
-    }
-    if (0 >
-        TALER_amount_add (&pc->total_fees_paid,
-                          &pc->total_fees_paid,
-                          deposit_fee))
-    {
-      /* We accepted this coin for payment on this contract before,
-         and now we can't even add the amount!? */
-      GNUNET_break (0);
-      continue;
-    }
+                "Deposit of coin `%s' already in our DB.\n",
+                TALER_B2S (coin_pub));
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&pc->total_paid,
+                                     &pc->total_paid,
+                                     amount_with_fee));
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&pc->total_fees_paid,
+                                     &pc->total_fees_paid,
+                                     deposit_fee));
     dc->deposit_fee = *deposit_fee;
     dc->refund_fee = *refund_fee;
     dc->wire_fee = *wire_fee;
     dc->amount_with_fee = *amount_with_fee;
-    dc->found_in_db = GNUNET_YES;
+    dc->found_in_db = true;
     pc->pending--;
   }
 }
 
 
 /**
- * Try to parse the pay request into the given pay context.
- * Schedules an error response in the connection on failure.
+ * Function called with information about a refund.  Check if this coin was
+ * claimed by the wallet for the transaction, and if so add the refunded
+ * amount to the pc's "total_refunded" amount.
  *
- * @param connection HTTP connection we are receiving payment on
- * @param root JSON upload with payment data
- * @param pc context we use to handle the payment
- * @return #GNUNET_OK on success,
- *         #GNUNET_NO on failure (response was queued with MHD)
- *         #GNUNET_SYSERR on hard error (MHD connection must be dropped)
+ * @param cls closure with a `struct PayContext`
+ * @param coin_pub public coin from which the refund comes from
+ * @param refund_amount refund amount which is being taken from @a coin_pub
  */
-static enum GNUNET_GenericReturnValue
-parse_pay (struct MHD_Connection *connection,
-           const json_t *root,
-           struct PayContext *pc)
+static void
+check_coin_refunded (void *cls,
+                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                     const struct TALER_Amount *refund_amount)
 {
-  json_t *coins;
-  const char *order_id;
-  const char *mode;
-  struct TALER_MerchantPublicKeyP merchant_pub;
-  enum GNUNET_GenericReturnValue res;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_string ("mode",
-                             &mode),
-    GNUNET_JSON_spec_json ("coins",
-                           &coins),
-    GNUNET_JSON_spec_string ("order_id",
-                             &order_id),
-    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
-                                 &merchant_pub),
-    GNUNET_JSON_spec_end ()
-  };
-  enum GNUNET_DB_QueryStatus qs;
+  struct PayContext *pc = cls;
 
-  res = TALER_MHD_parse_json_data (connection,
-                                   root,
-                                   spec);
-  if (GNUNET_YES != res)
+  for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
-    GNUNET_break_op (0);
-    return res;
-  }
+    struct DepositConfirmation *dc = &pc->dc[i];
 
-  if (0 != GNUNET_memcmp (&merchant_pub,
-                          &pc->mi->pubkey))
-  {
-    GNUNET_JSON_parse_free (spec);
-    TALER_LOG_INFO (
-      "Unknown merchant public key included in payment (usually wrong instance 
chosen)\n");
-    return
-      (MHD_YES ==
-       TALER_MHD_reply_with_error (connection,
-                                   MHD_HTTP_BAD_REQUEST,
-                                   TALER_EC_PAY_WRONG_INSTANCE,
-                                   "merchant_pub in contract does not match 
this instance"))
-      ? GNUNET_NO
-      : GNUNET_SYSERR;
+    /* Get matching coin from results*/
+    if (0 != GNUNET_memcmp (coin_pub,
+                            &dc->coin_pub))
+      continue;
+    GNUNET_assert (0 <=
+                   TALER_amount_add (&pc->total_refunded,
+                                     &pc->total_refunded,
+                                     refund_amount));
   }
+}
 
-  {
-    const char *session_id;
 
-    session_id = json_string_value (json_object_get (root,
-                                                     "session_id"));
-    if (NULL != session_id)
-      pc->session_id = GNUNET_strdup (session_id);
-  }
-  GNUNET_assert (NULL == pc->order_id);
-  pc->order_id = GNUNET_strdup (order_id);
-  GNUNET_assert (NULL == pc->contract_terms);
-  qs = db->find_contract_terms (db->cls,
-                                &pc->contract_terms,
-                                order_id,
-                                &merchant_pub);
-  if (0 > qs)
-  {
-    GNUNET_JSON_parse_free (spec);
-    /* single, read-only SQL statements should never cause
-       serialization problems */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    return
-      (MHD_YES ==
-       TALER_MHD_reply_with_error (connection,
-                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                   TALER_EC_PAY_DB_FETCH_PAY_ERROR,
-                                   "Failed to obtain contract terms from DB"))
-      ? GNUNET_NO
-      : GNUNET_SYSERR;
-  }
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-  {
-    GNUNET_JSON_parse_free (spec);
-    return
-      (MHD_YES ==
-       TALER_MHD_reply_with_error (connection,
-                                   MHD_HTTP_NOT_FOUND,
-                                   TALER_EC_PAY_PROPOSAL_NOT_FOUND,
-                                   "Proposal not found"))
-      ? GNUNET_NO
-      : GNUNET_SYSERR;
-  }
-
-  if (GNUNET_OK !=
-      TALER_JSON_hash (pc->contract_terms,
-                       &pc->h_contract_terms))
-  {
-    GNUNET_break (0);
-    GNUNET_JSON_parse_free (spec);
-    return
-      (MHD_YES ==
-       TALER_MHD_reply_with_error (connection,
-                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                   TALER_EC_PAY_FAILED_COMPUTE_PROPOSAL_HASH,
-                                   "Failed to hash proposal"))
-      ? GNUNET_NO
-      : GNUNET_SYSERR;
-  }
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Handling /pay for order `%s' with contract hash `%s'\n",
-              order_id,
-              GNUNET_h2s (&pc->h_contract_terms));
+/**
+ * Check whether the amount paid is sufficient to cover the price.
+ *
+ * @param pc payment context to check
+ * @return true if the payment is sufficient, false if it is
+ *         insufficient
+ */
+static bool
+check_payment_sufficient (struct PayContext *pc)
+{
+  struct TALER_Amount acc_fee;
+  struct TALER_Amount acc_amount;
+  struct TALER_Amount final_amount;
+  struct TALER_Amount wire_fee_delta;
+  struct TALER_Amount wire_fee_customer_contribution;
+  struct TALER_Amount total_wire_fee;
+  struct TALER_Amount total_needed;
 
-  if (NULL == json_object_get (pc->contract_terms,
-                               "merchant"))
-  {
-    /* invalid contract */
-    GNUNET_JSON_parse_free (spec);
-    return
-      (MHD_YES ==
-       TALER_MHD_reply_with_error (connection,
-                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                   TALER_EC_PAY_MERCHANT_FIELD_MISSING,
-                                   "No merchant field in proposal"))
-      ? GNUNET_NO
-      : GNUNET_SYSERR;
-  }
-  if (0 != strcasecmp ("abort-refund",
-                       mode))
-    pc->mode = PC_MODE_PAY;
-  else
-    pc->mode = PC_MODE_ABORT_REFUND;
+  GNUNET_assert (0 != pc->coins_cnt);
+  acc_fee = pc->dc[0].deposit_fee;
+  total_wire_fee = pc->dc[0].wire_fee;
+  acc_amount = pc->dc[0].amount_with_fee;
+
+  /**
+   * This loops calculates what are the deposit fee / total
+   * amount with fee / and wire fee, for all the coins.
+   */
+  for (unsigned int i = 1; i<pc->coins_cnt; i++)
   {
-    const char *fulfillment_url;
-    struct GNUNET_JSON_Specification espec[] = {
-      GNUNET_JSON_spec_absolute_time ("refund_deadline",
-                                      &pc->refund_deadline),
-      GNUNET_JSON_spec_absolute_time ("pay_deadline",
-                                      &pc->pay_deadline),
-      GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline",
-                                      &pc->wire_transfer_deadline),
-      GNUNET_JSON_spec_absolute_time ("timestamp",
-                                      &pc->timestamp),
-      TALER_JSON_spec_amount ("max_fee",
-                              &pc->max_fee),
-      TALER_JSON_spec_amount ("amount",
-                              &pc->amount),
-      GNUNET_JSON_spec_string ("fulfillment_url",
-                               &fulfillment_url),
-      GNUNET_JSON_spec_fixed_auto ("h_wire",
-                                   &pc->h_wire),
-      GNUNET_JSON_spec_end ()
-    };
+    struct DepositConfirmation *dc = &pc->dc[i];
 
-    res = TALER_MHD_parse_json_data (connection,
-                                     pc->contract_terms,
-                                     espec);
-    if (GNUNET_YES != res)
+    GNUNET_assert (dc->found_in_db);
+    if ( (0 >
+          TALER_amount_add (&acc_fee,
+                            &dc->deposit_fee,
+                            &acc_fee)) ||
+         (0 >
+          TALER_amount_add (&acc_amount,
+                            &dc->amount_with_fee,
+                            &acc_amount)) )
     {
       GNUNET_break (0);
-      GNUNET_JSON_parse_free (spec);
-      return res;
+      /* Overflow in these amounts? Very strange. */
+      resume_pay_with_error (pc,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_PAY_AMOUNT_OVERFLOW,
+                             "Overflow adding up amounts");
     }
-
-    pc->fulfillment_url = GNUNET_strdup (fulfillment_url);
-    if (pc->wire_transfer_deadline.abs_value_us <
-        pc->refund_deadline.abs_value_us)
+    if (1 ==
+        TALER_amount_cmp (&dc->deposit_fee,
+                          &dc->amount_with_fee))
     {
-      /* This should already have been checked when creating the
-         order! */
-      GNUNET_break (0);
-      GNUNET_JSON_parse_free (spec);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
-                                         "refund deadline after wire transfer 
deadline");
+      GNUNET_break_op (0);
+      resume_pay_with_error (pc,
+                             MHD_HTTP_BAD_REQUEST,
+                             TALER_EC_PAY_FEES_EXCEED_PAYMENT,
+                             "Deposit fees exceed coin's contribution");
+      return false;
     }
 
-    if (pc->pay_deadline.abs_value_us <
-        GNUNET_TIME_absolute_get ().abs_value_us)
+    /* If exchange differs, add wire fee */
     {
-      /* too late */
-      GNUNET_JSON_parse_free (spec);
-      return
-        (MHD_YES ==
-         TALER_MHD_reply_with_error (connection,
-                                     MHD_HTTP_GONE,
-                                     TALER_EC_PAY_OFFER_EXPIRED,
-                                     "The payment deadline has past and the 
offer is no longer valid"))
-        ? GNUNET_NO
-        : GNUNET_SYSERR;
-    }
-
-  }
+      bool new_exchange = true;
 
-  /* find wire method */
-  {
-    struct WireMethod *wm;
-
-    wm = pc->mi->wm_head;
-    while (0 != GNUNET_memcmp (&pc->h_wire,
-                               &wm->h_wire))
-      wm = wm->next;
-    if (NULL == wm)
-    {
-      GNUNET_break (0);
-      GNUNET_JSON_parse_free (spec);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_PAY_WIRE_HASH_UNKNOWN,
-                                         "Did not find matching wire details");
+      for (unsigned int j = 0; j<i; j++)
+        if (0 == strcasecmp (dc->exchange_url,
+                             pc->dc[j].exchange_url))
+        {
+          new_exchange = false;
+          break;
+        }
+      if (new_exchange)
+      {
+        if (GNUNET_OK !=
+            TALER_amount_cmp_currency (&total_wire_fee,
+                                       &dc->wire_fee))
+        {
+          GNUNET_break_op (0);
+          resume_pay_with_error (pc,
+                                 MHD_HTTP_PRECONDITION_FAILED,
+                                 TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
+                                 "exchange wire in different currency");
+          return false;
+        }
+        if (0 >
+            TALER_amount_add (&total_wire_fee,
+                              &total_wire_fee,
+                              &dc->wire_fee))
+        {
+          GNUNET_break (0);
+          resume_pay_with_error (pc,
+                                 MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                 
TALER_EC_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
+                                 "could not add exchange wire fee to total");
+          return false;
+        }
+      }
     }
-    pc->wm = wm;
   }
 
-  /* parse optional details */
-  if (NULL != json_object_get (pc->contract_terms,
-                               "max_wire_fee"))
-  {
-    struct GNUNET_JSON_Specification espec[] = {
-      TALER_JSON_spec_amount ("max_wire_fee",
-                              &pc->max_wire_fee),
-      GNUNET_JSON_spec_end ()
-    };
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Amount received from wallet: %s\n",
+              TALER_amount2s (&acc_amount));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Deposit fee for all coins: %s\n",
+              TALER_amount2s (&acc_fee));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Total wire fee: %s\n",
+              TALER_amount2s (&total_wire_fee));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Max wire fee: %s\n",
+              TALER_amount2s (&pc->max_wire_fee));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Deposit fee limit for merchant: %s\n",
+              TALER_amount2s (&pc->max_fee));
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Total refunded amount: %s\n",
+              TALER_amount2s (&pc->total_refunded));
 
-    res = TALER_MHD_parse_json_data (connection,
-                                     pc->contract_terms,
-                                     espec);
-    if (GNUNET_YES != res)
-    {
-      GNUNET_break_op (0); /* invalid input, fail */
-      GNUNET_JSON_parse_free (spec);
-      return res;
-    }
+  /* Now compare exchange wire fee compared to
+   * what we are willing to pay */
+  if (GNUNET_YES !=
+      TALER_amount_cmp_currency (&total_wire_fee,
+                                 &pc->max_wire_fee))
+  {
+    resume_pay_with_error (pc,
+                           MHD_HTTP_PRECONDITION_FAILED,
+                           TALER_EC_PAY_WIRE_FEE_CURRENCY_MISMATCH,
+                           "exchange wire does not match our currency");
+    return false;
   }
-  else
+
+  switch (TALER_amount_subtract (&wire_fee_delta,
+                                 &total_wire_fee,
+                                 &pc->max_wire_fee))
   {
-    /* default is we cover no fee */
+  case TALER_AAR_RESULT_POSITIVE:
+    /* Actual wire fee is indeed higher than our maximum,
+       compute how much the customer is expected to cover!  */
+    TALER_amount_divide (&wire_fee_customer_contribution,
+                         &wire_fee_delta,
+                         pc->wire_fee_amortization);
+    break;
+  case TALER_AAR_RESULT_ZERO:
+  case TALER_AAR_INVALID_NEGATIVE_RESULT:
+    /* Wire fee threshold is still above the wire fee amount.
+       Customer is not going to contribute on this.  */
     GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_get_zero (pc->max_fee.currency,
-                                          &pc->max_wire_fee));
+                   TALER_amount_get_zero (total_wire_fee.currency,
+                                          &wire_fee_customer_contribution));
+    break;
+  default:
+    GNUNET_assert (0);
   }
 
-  if (NULL != json_object_get (pc->contract_terms,
-                               "wire_fee_amortization"))
+  /* add wire fee contribution to the total fees */
+  if (0 >
+      TALER_amount_add (&acc_fee,
+                        &acc_fee,
+                        &wire_fee_customer_contribution))
   {
-    struct GNUNET_JSON_Specification espec[] = {
-      GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
-                               &pc->wire_fee_amortization),
-      GNUNET_JSON_spec_end ()
-    };
+    GNUNET_break (0);
+    resume_pay_with_error (pc,
+                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                           TALER_EC_PAY_AMOUNT_OVERFLOW,
+                           "Overflow adding up amounts");
+    return false;
+  }
+  if (-1 == TALER_amount_cmp (&pc->max_fee,
+                              &acc_fee))
+  {
+    /**
+     * Sum of fees of *all* the different exchanges of all the coins are
+     * higher than the fixed limit that the merchant is willing to pay.  The
+     * difference must be paid by the customer.
+     *///
+    struct TALER_Amount excess_fee;
 
-    res = TALER_MHD_parse_json_data (connection,
-                                     pc->contract_terms,
-                                     espec);
-    if ( (GNUNET_YES != res) ||
-         (0 == pc->wire_fee_amortization) )
+    /* compute fee amount to be covered by customer */
+    GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
+                   TALER_amount_subtract (&excess_fee,
+                                          &acc_fee,
+                                          &pc->max_fee));
+    /* add that to the total */
+    if (0 >
+        TALER_amount_add (&total_needed,
+                          &excess_fee,
+                          &pc->amount))
     {
-      GNUNET_break_op (0); /* invalid input, use default */
-      /* default is no amortization */
-      pc->wire_fee_amortization = 1;
+      GNUNET_break (0);
+      resume_pay_with_error (pc,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_PAY_AMOUNT_OVERFLOW,
+                             "Overflow adding up amounts");
+      return false;
     }
   }
   else
   {
-    pc->wire_fee_amortization = 1;
+    /* Fees are fully covered by the merchant, all we require
+       is that the total payment is not below the contract's amount */
+    total_needed = pc->amount;
   }
 
-  pc->coins_cnt = json_array_size (coins);
-  if (0 == pc->coins_cnt)
+  /* Do not count refunds towards the payment */
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Subtracting total refunds from paid amount: %s\n",
+              TALER_amount2s (&pc->total_refunded));
+  if (0 >
+      TALER_amount_subtract (&final_amount,
+                             &acc_amount,
+                             &pc->total_refunded))
   {
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PAY_COINS_ARRAY_EMPTY,
-                                       "coins");
+    GNUNET_break (0);
+    resume_pay_with_error (pc,
+                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                           TALER_EC_PAY_REFUNDS_EXCEED_PAYMENTS,
+                           "refunded amount exceeds total payments");
+    return false;
   }
-  /* note: 1 coin = 1 deposit confirmation expected */
-  pc->dc = GNUNET_new_array (pc->coins_cnt,
-                             struct DepositConfirmation);
 
-  /* This loop populates the array 'dc' in 'pc' */
+  if (-1 == TALER_amount_cmp (&final_amount,
+                              &total_needed))
   {
-    unsigned int coins_index;
-    json_t *coin;
-    json_array_foreach (coins, coins_index, coin)
+    /* acc_amount < total_needed */
+    if (-1 < TALER_amount_cmp (&acc_amount,
+                               &total_needed))
     {
-      struct DepositConfirmation *dc = &pc->dc[coins_index];
-      const char *exchange_url;
-      struct GNUNET_JSON_Specification ispec[] = {
-        TALER_JSON_spec_denomination_public_key ("denom_pub",
-                                                 &dc->denom),
-        TALER_JSON_spec_amount ("contribution",
-                                &dc->amount_with_fee),
-        GNUNET_JSON_spec_string ("exchange_url",
-                                 &exchange_url),
-        GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                     &dc->coin_pub),
-        TALER_JSON_spec_denomination_signature ("ub_sig",
-                                                &dc->ub_sig),
-        GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                     &dc->coin_sig),
-        GNUNET_JSON_spec_end ()
-      };
-
-      res = TALER_MHD_parse_json_data (connection,
-                                       coin,
-                                       ispec);
-      if (GNUNET_YES != res)
-      {
-        GNUNET_JSON_parse_free (spec);
-        GNUNET_break_op (0);
-        return res;
-      }
-      dc->exchange_url = GNUNET_strdup (exchange_url);
-      dc->index = coins_index;
-      dc->pc = pc;
+      resume_pay_with_error (pc,
+                             MHD_HTTP_PAYMENT_REQUIRED,
+                             TALER_EC_PAY_REFUNDED,
+                             "contract not paid up due to refunds");
     }
-  }
-  pc->pending = pc->coins_cnt;
-  GNUNET_JSON_parse_free (spec);
-  return GNUNET_OK;
-}
-
-
-/**
- * Function called with information about a refund.
- * Check if this coin was claimed by the wallet for the
- * transaction, and if so add the refunded amount to the
- * pc's "total_refunded" amount.
- *
- * @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 @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,
-                     const struct TALER_Amount *refund_fee)
-{
-  struct PayContext *pc = cls;
-
-  (void) exchange_url;
-  for (unsigned int i = 0; i<pc->coins_cnt; i++)
-  {
-    struct DepositConfirmation *dc = &pc->dc[i];
-
-    /* Get matching coin from results*/
-    if (0 == GNUNET_memcmp (coin_pub,
-                            &dc->coin_pub))
+    else if (-1 < TALER_amount_cmp (&acc_amount,
+                                    &pc->amount))
+    {
+      GNUNET_break_op (0);
+      resume_pay_with_error (pc,
+                             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)");
+    }
+    else
     {
-      dc->refunded = GNUNET_YES;
-      GNUNET_assert (0 <=
-                     TALER_amount_add (&pc->total_refunded,
-                                       &pc->total_refunded,
-                                       refund_amount));
+      GNUNET_break_op (0);
+      resume_pay_with_error (pc,
+                             MHD_HTTP_NOT_ACCEPTABLE,
+                             TALER_EC_PAY_PAYMENT_INSUFFICIENT,
+                             "payment insufficient");
     }
+    return false;
   }
+  return true;
 }
 
 
 /**
- * Begin of the DB transaction.  If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
+ * Begin of the DB transaction for a payment.  If required (from
+ * soft/serialization errors), the transaction can be restarted here.
  *
- * @param pc payment context to transact
+ * @param[in,out] pc payment context to transact
  */
 static void
 begin_transaction (struct PayContext *pc)
 {
   enum GNUNET_DB_QueryStatus qs;
+  struct TMH_HandlerContext *hc = pc->hc;
+  const char *instance_id = hc->instance->settings.id;
 
   /* Avoid re-trying transactions on soft errors forever! */
   if (pc->retry_counter++ > MAX_RETRIES)
@@ -1806,7 +1265,9 @@ begin_transaction (struct PayContext *pc)
   }
   GNUNET_assert (GNUNET_YES == pc->suspended);
 
-  /* Init. some price accumulators.  */
+  /* Initialize some amount accumulators
+     (used in check_coin_paid(), check_coin_refunded()
+     and check_payment_sufficient()). */
   GNUNET_break (GNUNET_OK ==
                 TALER_amount_get_zero (pc->amount.currency,
                                        &pc->total_paid));
@@ -1816,12 +1277,15 @@ begin_transaction (struct PayContext *pc)
   GNUNET_break (GNUNET_OK ==
                 TALER_amount_get_zero (pc->amount.currency,
                                        &pc->total_refunded));
+  for (unsigned int i = 0; i<pc->coins_cnt; i++)
+    pc->dc[i].found_in_db = false;
+  pc->pending = pc->coins_cnt;
 
   /* First, try to see if we have all we need already done */
-  db->preflight (db->cls);
+  TMH_db->preflight (TMH_db->cls);
   if (GNUNET_OK !=
-      db->start (db->cls,
-                 "run pay"))
+      TMH_db->start (TMH_db->cls,
+                     "run pay"))
   {
     GNUNET_break (0);
     resume_pay_with_error (pc,
@@ -1832,14 +1296,14 @@ begin_transaction (struct PayContext *pc)
   }
 
   /* Check if some of these coins already succeeded for _this_ contract.  */
-  qs = db->find_payments (db->cls,
-                          &pc->h_contract_terms,
-                          &pc->mi->pubkey,
-                          &check_coin_paid,
-                          pc);
+  qs = TMH_db->lookup_deposits (TMH_db->cls,
+                                instance_id,
+                                &pc->h_contract_terms,
+                                &check_coin_paid,
+                                pc);
   if (0 > qs)
   {
-    db->rollback (db->cls);
+    TMH_db->rollback (TMH_db->cls);
     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
       begin_transaction (pc);
@@ -1855,14 +1319,14 @@ begin_transaction (struct PayContext *pc)
   }
 
   /* Check if we refunded some of the coins */
-  qs = db->get_refunds_from_contract_terms_hash (db->cls,
-                                                 &pc->mi->pubkey,
-                                                 &pc->h_contract_terms,
-                                                 &check_coin_refunded,
-                                                 pc);
+  qs = TMH_db->lookup_refunds (TMH_db->cls,
+                               instance_id,
+                               &pc->h_contract_terms,
+                               &check_coin_refunded,
+                               pc);
   if (0 > qs)
   {
-    db->rollback (db->cls);
+    TMH_db->rollback (TMH_db->cls);
     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
       begin_transaction (pc);
@@ -1877,339 +1341,468 @@ begin_transaction (struct PayContext *pc)
     return;
   }
 
-  /* All the coins known to the database have
-   * been processed, now delve into specific case
-   * (pay vs. abort) */
+  /* Check if there are coins that still need to be processed */
 
-  if (PC_MODE_ABORT_REFUND == pc->mode)
+  if (0 != pc->pending)
   {
-    json_t *terms;
+    /* we made no DB changes, so we can just rollback */
+    TMH_db->rollback (TMH_db->cls);
 
-    /* The wallet is going for a refund,
-       (on aborted operation)! */
+    /* Ok, we need to first go to the network to process more coins.
+       We that interaction in *tiny* transactions (hence the rollback
+       above). */
+    find_next_exchange (pc);
+    return;
+  }
 
-    /* check payment was indeed incomplete */
-    qs = db->find_paid_contract_terms_from_hash (db->cls,
-                                                 &terms,
-                                                 &pc->h_contract_terms,
-                                                 &pc->mi->pubkey);
-    if (0 > qs)
-    {
-      db->rollback (db->cls);
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        begin_transaction (pc);
-        return;
-      }
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_PAY_DB_STORE_PAY_ERROR,
-                             "Merchant database error");
-      return;
-    }
-    if (0 < qs)
+  /* 0 == pc->pending: all coins processed, let's see if that was enough */
+  if (! check_payment_sufficient (pc))
+  {
+    /* check_payment_sufficient() will have queued an error already.
+       We need to still abort the transaction. */
+    TMH_db->rollback (TMH_db->cls);
+    return;
+  }
+  /* Payment succeeded, save in database */
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Order `%s' (%s) was fully paid\n",
+              pc->order_id,
+              GNUNET_h2s (&pc->h_contract_terms));
+  qs = TMH_db->mark_contract_paid (TMH_db->cls,
+                                   instance_id,
+                                   &pc->h_contract_terms,
+                                   pc->session_id);
+  if (qs < 0)
+  {
+    TMH_db->rollback (TMH_db->cls);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
-      /* Payment had been complete! */
-      json_decref (terms);
-      db->rollback (db->cls);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_FORBIDDEN,
-                             
TALER_EC_PAY_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE,
-                             "Payment complete, refusing to abort");
+      begin_transaction (pc);
       return;
     }
+    resume_pay_with_error (pc,
+                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                           TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
+                           "Could not set contract to 'paid' in DB");
+    return;
+  }
 
-    /* Store refund in DB */
-    qs = db->increase_refund_for_contract_NT (db->cls,
-                                              &pc->h_contract_terms,
-                                              &pc->mi->pubkey,
-                                              &pc->total_paid,
-                                              /* justification */
-                                              "incomplete payment aborted");
-    if (0 > qs)
+  /* Now commit! */
+  if (0 <= qs)
+    qs = TMH_db->commit (TMH_db->cls);
+  else
+    TMH_db->rollback (TMH_db->cls);
+  if (0 > qs)
+  {
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
-      db->rollback (db->cls);
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        begin_transaction (pc);
-        return;
-      }
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_PAY_DB_STORE_PAY_ERROR,
-                             "Merchant database error storing abort-refund");
+      begin_transaction (pc);
       return;
     }
-    qs = db->commit (db->cls);
-    if (0 > qs)
+    resume_pay_with_error (pc,
+                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                           TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
+                           "Database commit mark proposal as 'paid' failed");
+    return;
+  }
+
+  /* Notify clients that have been waiting for the payment to succeed */
+  TMH_long_poll_resume (pc->order_id,
+                        hc->instance,
+                        NULL);
+
+  /* Generate response (payment successful) */
+  {
+    struct GNUNET_CRYPTO_EddsaSignature sig;
+
+    /* Sign on our end (as the payment did go through, even if it may
+       have been refunded already) */
     {
-      db->rollback (db->cls);
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        begin_transaction (pc);
-        return;
-      }
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_PAY_DB_STORE_PAY_ERROR,
-                             "Merchant database error: could not commit");
-      return;
+      struct PaymentResponsePS mr = {
+        .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK),
+        .purpose.size = htonl (sizeof (mr)),
+        .h_contract_terms = pc->h_contract_terms
+      };
+
+      GNUNET_CRYPTO_eddsa_sign (&pc->hc->instance->merchant_priv.eddsa_priv,
+                                &mr,
+                                &sig);
     }
-    /* At this point, the refund got correctly committed
-     * into the database.  */
+
+    /* Build the response */
     {
-      json_t *refunds;
+      json_t *resp;
 
-      refunds = json_array ();
-      if (NULL == refunds)
+      resp = json_pack ("{s:o}",
+                        "sig",
+                        GNUNET_JSON_from_data_auto (&sig));
+      if (NULL == resp)
       {
         GNUNET_break (0);
         resume_pay_with_error (pc,
                                MHD_HTTP_INTERNAL_SERVER_ERROR,
                                TALER_EC_JSON_ALLOCATION_FAILURE,
-                               "could not create JSON array");
+                               "Could not build final response");
         return;
       }
-      for (unsigned int i = 0; i<pc->coins_cnt; i++)
+      resume_pay_with_response (pc,
+                                MHD_HTTP_OK,
+                                TALER_MHD_make_json (resp));
+      json_decref (resp);
+    }
+  }
+}
+
+
+/**
+ * Try to parse the pay request into the given pay context.
+ * Schedules an error response in the connection on failure.
+ *
+ * @param connection HTTP connection we are receiving payment on
+ * @param[in,out] hc context with further information about the request
+ * @param pc context we use to handle the payment
+ * @return #GNUNET_OK on success,
+ *         #GNUNET_NO on failure (response was queued with MHD)
+ *         #GNUNET_SYSERR on hard error (MHD connection must be dropped)
+ */
+static enum GNUNET_GenericReturnValue
+parse_pay (struct MHD_Connection *connection,
+           struct TMH_HandlerContext *hc,
+           struct PayContext *pc)
+{
+  /* First, parse request */
+  {
+    json_t *coins;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_json ("coins",
+                             &coins),
+      GNUNET_JSON_spec_end ()
+    };
+
+    {
+      enum GNUNET_GenericReturnValue res;
+
+      res = TALER_MHD_parse_json_data (connection,
+                                       hc->request_body,
+                                       spec);
+      if (GNUNET_YES != res)
+      {
+        GNUNET_break_op (0);
+        return res;
+      }
+    }
+
+    if ( (! json_is_array (coins)) ||
+         (0 == (pc->coins_cnt = json_array_size (coins))) )
+    {
+      GNUNET_break_op (0);
+      GNUNET_JSON_parse_free (spec);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_PAY_COINS_ARRAY_EMPTY,
+                                         "'coins' array is empty or not even 
an array");
+    }
+
+    /* note: 1 coin = 1 deposit confirmation expected */
+    pc->dc = GNUNET_new_array (pc->coins_cnt,
+                               struct DepositConfirmation);
+
+    /* This loop populates the array 'dc' in 'pc' */
+    {
+      unsigned int coins_index;
+      json_t *coin;
+      json_array_foreach (coins, coins_index, coin)
       {
-        struct TALER_MerchantSignatureP msig;
-        struct TALER_RefundRequestPS rr = {
-          .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND),
-          .purpose.size = htonl (sizeof (rr)),
-          .h_contract_terms = pc->h_contract_terms,
-          .coin_pub = pc->dc[i].coin_pub,
-          .merchant = pc->mi->pubkey,
-          .rtransaction_id = GNUNET_htonll (0)
+        struct DepositConfirmation *dc = &pc->dc[coins_index];
+        const char *exchange_url;
+        enum GNUNET_GenericReturnValue res;
+        struct GNUNET_JSON_Specification ispec[] = {
+          GNUNET_JSON_spec_fixed_auto ("h_denom",
+                                       &dc->h_denom),
+          TALER_JSON_spec_amount ("contribution",
+                                  &dc->amount_with_fee),
+          GNUNET_JSON_spec_string ("exchange_url",
+                                   &exchange_url),
+          GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                       &dc->coin_pub),
+          TALER_JSON_spec_denomination_signature ("ub_sig",
+                                                  &dc->ub_sig),
+          GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                       &dc->coin_sig),
+          GNUNET_JSON_spec_end ()
         };
 
-        if (GNUNET_YES != pc->dc[i].found_in_db)
-          continue; /* Skip coins not found in DB.  */
-        TALER_amount_hton (&rr.refund_amount,
-                           &pc->dc[i].amount_with_fee);
-        TALER_amount_hton (&rr.refund_fee,
-                           &pc->dc[i].refund_fee);
-
-        GNUNET_CRYPTO_eddsa_sign (&pc->mi->privkey.eddsa_priv,
-                                  &rr,
-                                  &msig.eddsa_sig);
-        /* Pack refund for i-th coin.  */
-        if (0 !=
-            json_array_append_new (
-              refunds,
-              json_pack ("{s:I, s:o, s:o s:o s:o}",
-                         "rtransaction_id",
-                         (json_int_t) 0,
-                         "coin_pub",
-                         GNUNET_JSON_from_data_auto (&rr.coin_pub),
-                         "merchant_sig",
-                         GNUNET_JSON_from_data_auto (&msig),
-                         "refund_amount",
-                         TALER_JSON_from_amount_nbo (&rr.refund_amount),
-                         "refund_fee",
-                         TALER_JSON_from_amount_nbo (&rr.refund_fee))))
+        res = TALER_MHD_parse_json_data (connection,
+                                         coin,
+                                         ispec);
+        if (GNUNET_YES != res)
         {
-          json_decref (refunds);
-          GNUNET_break (0);
-          resume_pay_with_error (pc,
-                                 MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                 TALER_EC_JSON_ALLOCATION_FAILURE,
-                                 "could not create JSON array");
-          return;
+          GNUNET_JSON_parse_free (spec);
+          GNUNET_break_op (0);
+          return res;
         }
+        dc->exchange_url = GNUNET_strdup (exchange_url);
+        dc->index = coins_index;
+        dc->pc = pc;
       }
+    }
+    GNUNET_JSON_parse_free (spec);
+  }
 
-      /* Resume and send back the response.  */
-      resume_pay_with_response (
-        pc,
-        MHD_HTTP_OK,
-        TALER_MHD_make_json_pack (
-          "{s:o, s:o, s:o}",
-          /* Refunds pack.  */
-          "refund_permissions", refunds,
-          "merchant_pub",
-          GNUNET_JSON_from_data_auto (&pc->mi->pubkey),
-          "h_contract_terms",
-          GNUNET_JSON_from_data_auto (&pc->h_contract_terms)));
+  /* copy session ID (if set) */
+  {
+    const char *session_id;
+    json_t *sid;
+
+    sid = json_object_get (hc->request_body,
+                           "session_id");
+    if (NULL != sid)
+    {
+      if (! json_is_string (sid))
+      {
+        GNUNET_break_op (0);
+        return (MHD_YES ==
+                TALER_MHD_reply_with_error (connection,
+                                            MHD_HTTP_BAD_REQUEST,
+                                            TALER_EC_PARAMETER_MALFORMED,
+                                            "session_id"))
+               ? GNUNET_NO
+               : GNUNET_SYSERR;
+      }
+      session_id = json_string_value (sid);
+      GNUNET_assert (NULL != session_id);
+      pc->session_id = GNUNET_strdup (session_id);
     }
-    return;
-  } /* End of PC_MODE_ABORT_REFUND */
+  }
+
+  /* copy order ID */
+  {
+    const char *order_id = hc->infix;
 
-  /* Default PC_MODE_PAY mode */
+    GNUNET_assert (NULL != order_id);
+    GNUNET_assert (NULL == pc->order_id);
+    pc->order_id = GNUNET_strdup (order_id);
+  }
 
-  /* Final termination case: all coins already known, just
-     generate ultimate outcome. */
-  if (0 == pc->pending)
+  /* obtain contract terms */
   {
-    if (GNUNET_OK != check_payment_sufficient (pc))
+    enum GNUNET_DB_QueryStatus qs;
+    json_t *contract_terms = NULL;
+
+    qs = TMH_db->lookup_contract_terms (TMH_db->cls,
+                                        hc->instance->settings.id,
+                                        pc->order_id,
+                                        &contract_terms);
+    if (0 > qs)
     {
-      db->rollback (db->cls);
-      return;
+      /* single, read-only SQL statements should never cause
+         serialization problems */
+      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+      /* Always report on hard error to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                          TALER_EC_PAY_DB_FETCH_PAY_ERROR,
+                                          "Failed to obtain contract terms 
from DB"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
+    }
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+    {
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_NOT_FOUND,
+                                          TALER_EC_PAY_PROPOSAL_NOT_FOUND,
+                                          "Proposal not found"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
+    }
+
+    /* hash contract (needed later) */
+    if (GNUNET_OK !=
+        TALER_JSON_hash (contract_terms,
+                         &pc->h_contract_terms))
+    {
+      GNUNET_break (0);
+      json_decref (contract_terms);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                          
TALER_EC_PAY_FAILED_COMPUTE_PROPOSAL_HASH,
+                                          "Failed to hash contract terms"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
     }
-    /* Payment succeeded, save in database */
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Contract `%s' was fully paid\n",
+                "Handling payment for order `%s' with contract hash `%s'\n",
+                pc->order_id,
                 GNUNET_h2s (&pc->h_contract_terms));
-    qs = db->mark_proposal_paid (db->cls,
-                                 &pc->h_contract_terms,
-                                 &pc->mi->pubkey);
-    if (qs < 0)
+
+    /* basic sanity check on the contract */
+    if (NULL == json_object_get (contract_terms,
+                                 "merchant"))
+    {
+      /* invalid contract */
+      GNUNET_break (0);
+      json_decref (contract_terms);
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                          TALER_EC_PAY_MERCHANT_FIELD_MISSING,
+                                          "No merchant field in proposal"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
+    }
+
+    /* Get details from contract and check fundamentals */
     {
-      db->rollback (db->cls);
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      struct GNUNET_JSON_Specification espec[] = {
+        GNUNET_JSON_spec_absolute_time ("refund_deadline",
+                                        &pc->refund_deadline),
+        GNUNET_JSON_spec_absolute_time ("pay_deadline",
+                                        &pc->pay_deadline),
+        GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline",
+                                        &pc->wire_transfer_deadline),
+        GNUNET_JSON_spec_absolute_time ("timestamp",
+                                        &pc->timestamp),
+        TALER_JSON_spec_amount ("max_fee",
+                                &pc->max_fee),
+        TALER_JSON_spec_amount ("amount",
+                                &pc->amount),
+        GNUNET_JSON_spec_fixed_auto ("h_wire",
+                                     &pc->h_wire),
+        GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
+                                 &pc->wire_fee_amortization),
+        TALER_JSON_spec_amount ("max_wire_fee",
+                                &pc->max_wire_fee),
+        GNUNET_JSON_spec_end ()
+      };
+      enum GNUNET_GenericReturnValue res;
+
+      /* FIXME: this is a tad wrong, as IF the contract is
+         malformed, the routine generates an error that blames
+         it on the client (400) instead of on us! */
+      res = TALER_MHD_parse_json_data (connection,
+                                       contract_terms,
+                                       espec);
+      json_decref (contract_terms);
+      if (GNUNET_YES != res)
       {
-        begin_transaction (pc);
-        return;
+        GNUNET_break (0);
+        return res;
       }
-      resume_pay_with_error (
-        pc,
-        MHD_HTTP_INTERNAL_SERVER_ERROR,
-        TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
-        "Merchant database error: could not mark proposal as 'paid'");
-      return;
     }
 
-    if ( (NULL != pc->session_id) &&
-         (NULL != pc->fulfillment_url) )
+    if (pc->wire_transfer_deadline.abs_value_us <
+        pc->refund_deadline.abs_value_us)
     {
-      qs = db->insert_session_info (db->cls,
-                                    pc->session_id,
-                                    pc->fulfillment_url,
-                                    pc->order_id,
-                                    &pc->mi->pubkey);
+      /* This should already have been checked when creating the order! */
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
+                                         "refund deadline after wire transfer 
deadline");
     }
 
-    /* Now commit! */
-    if (0 <= qs)
-      qs = db->commit (db->cls);
-    else
-      db->rollback (db->cls);
-    if (0 > qs)
+    if (pc->pay_deadline.abs_value_us <
+        GNUNET_TIME_absolute_get ().abs_value_us)
     {
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        begin_transaction (pc);
-        return;
-      }
-      resume_pay_with_error (
-        pc,
-        MHD_HTTP_INTERNAL_SERVER_ERROR,
-        TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
-        "Merchant database error: could not commit to mark proposal as 
'paid'");
-      return;
+      /* too late */
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_GONE,
+                                          TALER_EC_PAY_OFFER_EXPIRED,
+                                          "We are past the payment deadline"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
     }
-    TMH_long_poll_resume (pc->order_id,
-                          &pc->mi->pubkey,
-                          NULL);
-    generate_success_response (pc);
-    return;
   }
 
+  /* Make sure wire method (still) exists for this instance */
+  {
+    struct TMH_WireMethod *wm;
 
-  /* we made no DB changes,
-     so we can just rollback */
-  db->rollback (db->cls);
+    wm = hc->instance->wm_head;
+    while (0 != GNUNET_memcmp (&pc->h_wire,
+                               &wm->h_wire))
+      wm = wm->next;
+    if (NULL == wm)
+    {
+      GNUNET_break (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_PAY_WIRE_HASH_UNKNOWN,
+                                         "Did not find matching wire details");
+    }
+    pc->wm = wm;
+  }
 
-  /* Ok, we need to first go to the network.
-     Do that interaction in *tiny* transactions. */
-  find_next_exchange (pc);
+  return GNUNET_OK;
 }
 
 
 /**
- * Process a payment for a proposal.
+ * Handle a timeout for the processing of the pay request.
  *
- * @param connection HTTP connection we are receiving payment on
- * @param root JSON upload with payment data
- * @param pc context we use to handle the payment
- * @return value to return to MHD (#MHD_NO to drop connection,
- *         #MHD_YES to keep handling it)
+ * @param cls our `struct PayContext`
  */
-static MHD_RESULT
-handler_pay_json (struct MHD_Connection *connection,
-                  const json_t *root,
-                  struct PayContext *pc)
+static void
+handle_pay_timeout (void *cls)
 {
-  {
-    enum GNUNET_GenericReturnValue ret;
-
-    ret = parse_pay (connection,
-                     root,
-                     pc);
-    if (GNUNET_OK != ret)
-      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
-  }
+  struct PayContext *pc = cls;
 
-  /* Payment not finished, suspend while we interact with the exchange */
-  MHD_suspend_connection (connection);
-  pc->suspended = GNUNET_YES;
+  pc->timeout_task = NULL;
+  GNUNET_assert (GNUNET_YES == pc->suspended);
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Suspending /pay handling while working with the exchange\n");
-  pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT,
-                                                   &handle_pay_timeout,
-                                                   pc);
-  begin_transaction (pc);
-  return MHD_YES;
+              "Resuming pay with error after timeout\n");
+  if (NULL != pc->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (pc->fo);
+    pc->fo = NULL;
+  }
+  resume_pay_with_error (pc,
+                         MHD_HTTP_REQUEST_TIMEOUT,
+                         TALER_EC_PAY_EXCHANGE_TIMEOUT,
+                         "likely the exchange did not reply quickly enough");
 }
 
 
 /**
- * Process a payment for a proposal.  Takes data from the given MHD
- * connection.
+ * Process a payment for a claimed order.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure
- *       (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a
- *       upload_data
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
 MH_handler_pay (struct TMH_RequestHandler *rh,
                 struct MHD_Connection *connection,
-                void **connection_cls,
-                const char *upload_data,
-                size_t *upload_data_size,
-                struct MerchantInstance *mi)
+                struct TMH_HandlerContext *hc)
 {
-  struct PayContext *pc;
-  enum GNUNET_GenericReturnValue res;
-  MHD_RESULT ret;
-  json_t *root;
+  struct PayContext *pc = hc->ctx;
 
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "In handler for /pay.\n");
-  if (NULL == *connection_cls)
+  if (NULL == pc)
   {
     pc = GNUNET_new (struct PayContext);
     GNUNET_CONTAINER_DLL_insert (pc_head,
                                  pc_tail,
                                  pc);
-    pc->hc.cc = &pay_context_cleanup;
     pc->connection = connection;
-    *connection_cls = pc;
-    pc->mi = mi;
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "/pay: picked instance %s\n",
-                mi->id);
-  }
-  else
-  {
-    /* not the first call, recover state */
-    pc = *connection_cls;
+    pc->hc = hc;
+    hc->ctx = pc;
+    hc->cc = &pay_context_cleanup;
   }
   if (GNUNET_SYSERR == pc->suspended)
     return MHD_NO; /* during shutdown, we don't generate any more replies */
   if (0 != pc->response_code)
   {
-    /* We are *done* processing the request,
-       just queue the response (!) */
+    MHD_RESULT res;
+
+    /* We are *done* processing the request, just queue the response (!) */
     if (UINT_MAX == pc->response_code)
     {
       GNUNET_break (0);
@@ -2221,35 +1814,34 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
     MHD_destroy_response (pc->response);
     pc->response = NULL;
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Queueing response (%u) for /pay (%s).\n",
+                "Queueing response (%u) for POST /orders/$ID/pay (%s).\n",
                 (unsigned int) pc->response_code,
                 res ? "OK" : "FAILED");
     return res;
   }
-
-  res = TALER_MHD_parse_post_json (connection,
-                                   &pc->json_parse_context,
-                                   upload_data,
-                                   upload_data_size,
-                                   &root);
-  if (GNUNET_SYSERR == res)
   {
-    GNUNET_break (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_JSON_INVALID,
-                                       "could not parse JSON");
+    enum GNUNET_GenericReturnValue ret;
+
+    ret = parse_pay (connection,
+                     hc,
+                     pc);
+    if (GNUNET_OK != ret)
+      return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
   }
-  if ( (GNUNET_NO == res) ||
-       (NULL == root) )
-    return MHD_YES; /* the POST's body has to be further fetched */
-
-  ret = handler_pay_json (connection,
-                          root,
-                          pc);
-  json_decref (root);
-  return ret;
+
+  /* Payment not finished, suspend while we interact with the exchange */
+  GNUNET_assert (GNUNET_NO == pc->suspended);
+  MHD_suspend_connection (connection);
+  pc->suspended = GNUNET_YES;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Suspending pay handling while working with the exchange\n");
+  GNUNET_assert (NULL == pc->timeout_task);
+  pc->timeout_task = GNUNET_SCHEDULER_add_delayed (PAY_TIMEOUT,
+                                                   &handle_pay_timeout,
+                                                   pc);
+  begin_transaction (pc);
+  return MHD_YES;
 }
 
 
-/* end of taler-merchant-httpd_pay.c */
+/* end of taler-merchant-httpd_post-orders-ID-pay.c */
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.h 
b/src/backend/taler-merchant-httpd_post-orders-ID-pay.h
index 726a27b..7cce41f 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.h
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  (C) 2014-2017 GNUnet e.V.
+  (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -14,12 +14,13 @@
   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
 /**
- * @file backend/taler-merchant-httpd_pay.h
- * @brief headers for /pay handler
+ * @file backend/taler-merchant-httpd_post-orders-ID-pay.h
+ * @brief headers for POST /orders/$ID/pay handler
  * @author Marcello Stanisci
+ * @author Christian Grothoff
  */
-#ifndef TALER_EXCHANGE_HTTPD_PAY_H
-#define TALER_EXCHANGE_HTTPD_PAY_H
+#ifndef TALER_EXCHANGE_HTTPD_POST_ORDERS_ID_PAY_H
+#define TALER_EXCHANGE_HTTPD_POST_ORDERS_ID_PAY_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
@@ -33,22 +34,16 @@ MH_force_pc_resume (void);
 
 
 /**
- * Manage a payment
+ * Process payment for an order.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
- * @param mi merchant backend instance, never NULL
+ * @param[in,out] hc context with further information about the request
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_pay (struct TMH_RequestHandler *rh,
-                struct MHD_Connection *connection,
-                void **connection_cls,
-                const char *upload_data,
-                size_t *upload_data_size,
-                struct MerchantInstance *mi);
+TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
+                        struct MHD_Connection *connection,
+                        struct TMH_HandlerContext *hc);
 
 #endif
diff --git a/src/include/taler_merchant_service.h 
b/src/include/taler_merchant_service.h
index a4312c0..3335aea 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -1391,6 +1391,7 @@ typedef void
 /**
  * Calls the POST /orders/$ID/claim API at the backend.  That is,
  * retrieve the final contract terms including the client nonce.
+ *
  * This is a PUBLIC API for wallets.
  *
  * @param ctx execution context
@@ -1419,188 +1420,112 @@ void
 TALER_MERCHANT_order_claim_cancel (struct TALER_MERCHANT_OrderClaimHandle 
*och);
 
 
-/* *********************   OLD ************************** */
-
+/**
+ * @brief Handle to a POST /orders/$ID/pay operation at a merchant.  Note that
+ * we use the same handle for interactions with frontends (API for wallets) or
+ * backends (API for frontends).  The difference is that for the frontend API,
+ * we need the private keys of the coins, while for the backend API we need
+ * the public keys and signatures received from the wallet.
+ */
+struct TALER_MERCHANT_OrderPayHandle;
 
-/* ********************* /refund ************************** */
 
 /**
- * Handle for a GET /refund operation.
+ * Callbacks of this type are used to serve the result of submitting a
+ * POST /orders/$ID/pay request to a merchant.
+ *
+ * @param cls closure
+ * @param hr HTTP response details
  */
-struct TALER_MERCHANT_RefundLookupOperation;
+typedef void
+(*TALER_MERCHANT_OrderPayCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr);
 
 
 /**
- * Detail about a refund lookup result.
+ * Information we need from the frontend (ok, the frontend sends just JSON)
+ * when forwarding a payment to the backend.
  */
-struct TALER_MERCHANT_RefundDetail
+struct TALER_MERCHANT_PaidCoin
 {
+  /**
+   * Denomination key with which the coin is signed
+   */
+  struct TALER_DenominationPublicKey denom_pub;
 
   /**
-   * Exchange response details.  Full details are only included
-   * upon failure (HTTP status is not #MHD_HTTP_OK).
+   * Exchange’s unblinded signature of the coin
    */
-  struct TALER_EXCHANGE_HttpResponse hr;
+  struct TALER_DenominationSignature denom_sig;
 
   /**
-   * Coin this detail is about.
+   * Overall value that coins of this @e denom_pub have.
+   */
+  struct TALER_Amount denom_value;
+
+  /**
+   * Coin's public key.
    */
   struct TALER_CoinSpendPublicKeyP coin_pub;
 
   /**
-   * Refund transaction ID used.
+   * Coin's signature key.
    */
-  uint64_t rtransaction_id;
+  struct TALER_CoinSpendSignatureP coin_sig;
 
   /**
-   * Amount to be refunded for this coin.
+   * Amount this coin contributes to (including fee).
    */
-  struct TALER_Amount refund_amount;
+  struct TALER_Amount amount_with_fee;
 
   /**
-   * Applicable refund transaction fee.
+   * Amount this coin contributes to (without fee).
    */
-  struct TALER_Amount refund_fee;
+  struct TALER_Amount amount_without_fee;
 
   /**
-   * Public key of the exchange affirming the refund,
-   * only valid if the @e hr http_status is #MHD_HTTP_OK.
+   * Fee the exchange charges for refunds of this coin.
    */
-  struct TALER_ExchangePublicKeyP exchange_pub;
+  struct TALER_Amount refund_fee;
 
   /**
-   * Signature of the exchange affirming the refund,
-   * only valid if the @e hr http_status is #MHD_HTTP_OK.
+   * What is the URL of the exchange that issued @a coin_pub?
    */
-  struct TALER_ExchangeSignatureP exchange_sig;
+  const char *exchange_url;
 
 };
 
 
 /**
- * Callback to process a GET /refund request
+ * Pay a merchant.  API for frontends talking to backends. Here,
+ * the frontend does not have the coin's private keys, but just
+ * the public keys and signatures.  Note the sublte difference
+ * in the type of @a coins compared to #TALER_MERCHANT_pay().
  *
- * @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 GNUNET_HashCode *h_contract_terms,
-  const struct TALER_MerchantPublicKeyP *merchant_pub,
-  unsigned int num_details,
-  const struct TALER_MERCHANT_RefundDetail *details);
-
-
-/**
- * Does a GET /refund.
+ * This is a PUBLIC API, albeit in this form useful for the frontend,
+ * in case the frontend is proxying the request.
  *
  * @param ctx execution context
- * @param backend_url base URL of the merchant backend
- * @param order_id order id used to perform the lookup
- * @param cb callback which will work the response gotten from the backend
- * @param cb_cls closure to pass to the callback
- * @return handle for this operation, NULL upon errors
- */
-struct TALER_MERCHANT_RefundLookupOperation *
-TALER_MERCHANT_refund_lookup (struct GNUNET_CURL_Context *ctx,
-                              const char *backend_url,
-                              const char *order_id,
-                              TALER_MERCHANT_RefundLookupCallback cb,
-                              void *cb_cls);
-
-/**
- * Cancel a GET /refund request.
- *
- * @param rlo the refund increasing operation to cancel
- */
-void
-TALER_MERCHANT_refund_lookup_cancel (
-  struct TALER_MERCHANT_RefundLookupOperation *rlo);
-
-
-/**
- * Handle for a POST /refund operation.
- */
-struct TALER_MERCHANT_RefundIncreaseOperation;
-
-
-/**
- * Callback to process a POST /refund request
- *
- * @param cls closure
- * @param http_status HTTP status code for this request
- * @param ec taler-specific error code
- */
-typedef void
-(*TALER_MERCHANT_RefundIncreaseCallback) (
-  void *cls,
-  const struct TALER_MERCHANT_HttpResponse *hr);
-
-
-/**
- * Increase the refund associated to a order
- *
- * @param ctx the CURL context used to connect to the backend
- * @param backend_url backend's base URL, including final "/"
- * @param order_id id of the order whose refund is to be increased
- * @param refund amount to which increase the refund
- * @param reason human-readable reason justifying the refund
- * @param cb callback processing the response from /refund
- * @param cb_cls closure for cb
- */
-struct TALER_MERCHANT_RefundIncreaseOperation *
-TALER_MERCHANT_refund_increase (struct GNUNET_CURL_Context *ctx,
-                                const char *backend_url,
-                                const char *order_id,
-                                const struct TALER_Amount *refund,
-                                const char *reason,
-                                TALER_MERCHANT_RefundIncreaseCallback cb,
-                                void *cb_cls);
-
-/**
- * Cancel a POST /refund request.
- *
- * @param rio the refund increasing operation to cancel
- */
-void
-TALER_MERCHANT_refund_increase_cancel (
-  struct TALER_MERCHANT_RefundIncreaseOperation *rio);
-
-
-/* *********************  /proposal *********************** */
-
-
-/* *********************  /pay *********************** */
-
-
-/**
- * @brief Handle to a /pay operation at a merchant.  Note that we use
- * the same handle for interactions with frontends (API for wallets)
- * or backends (API for frontends).  The difference is that for the
- * frontend API, we need the private keys of the coins, while for
- * the backend API we need the public keys and signatures received
- * from the wallet.  Also, the frontend returns a redirect URL on
- * success, while the backend just returns a success status code.
- */
-struct TALER_MERCHANT_Pay;
-
-
-/**
- * Callbacks of this type are used to serve the result of submitting a
- * /pay request to a merchant.
- *
- * @param cls closure
- * @param hr HTTP response details
+ * @param merchant_url base URL of the merchant
+ * @param merchant_pub public key of the merchant
+ * @param order_id which order should be paid
+ * @param num_coins length of the @a coins array
+ * @param coins array of coins to pay with
+ * @param pay_cb the callback to call when a reply for this request is 
available
+ * @param pay_cb_cls closure for @a pay_cb
+ * @return a handle for this request
  */
-typedef void
-(*TALER_MERCHANT_PayCallback) (void *cls,
-                               const struct TALER_MERCHANT_HttpResponse *hr);
+struct TALER_MERCHANT_OrderPayHandle *
+TALER_MERCHANT_order_pay_frontend (
+  struct GNUNET_CURL_Context *ctx,
+  const char *merchant_url,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const char *order_id,
+  unsigned int num_coins,
+  const struct TALER_MERCHANT_PaidCoin coins[],
+  TALER_MERCHANT_PayCallback pay_cb,
+  void *pay_cb_cls);
 
 
 /**
@@ -1655,6 +1580,8 @@ struct TALER_MERCHANT_PayCoin
 /**
  * Pay a merchant.  API for wallets that have the coin's private keys.
  *
+ * This is a PUBLIC API for wallets.
+ *
  * @param ctx execution context
  * @param merchant_url base URL of the merchant
  * @param h_wire hash of the merchant’s account details
@@ -1674,29 +1601,50 @@ struct TALER_MERCHANT_PayCoin
  * @param pay_cb_cls closure for @a pay_cb
  * @return a handle for this request
  */
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
-                           const char *merchant_url,
-                           const struct GNUNET_HashCode *h_contract,
-                           const struct TALER_Amount *amount,
-                           const struct TALER_Amount *max_fee,
-                           const struct TALER_MerchantPublicKeyP *merchant_pub,
-                           const struct TALER_MerchantSignatureP *merchant_sig,
-                           struct GNUNET_TIME_Absolute timestamp,
-                           struct GNUNET_TIME_Absolute refund_deadline,
-                           struct GNUNET_TIME_Absolute pay_deadline,
-                           const struct GNUNET_HashCode *h_wire,
-                           const char *order_id,
-                           unsigned int num_coins,
-                           const struct TALER_MERCHANT_PayCoin *coins,
-                           TALER_MERCHANT_PayCallback pay_cb,
-                           void *pay_cb_cls);
+struct TALER_MERCHANT_OrderPayHandle *
+TALER_MERCHANT_order_pay (
+  struct GNUNET_CURL_Context *ctx,
+  const char *merchant_url,
+  const struct GNUNET_HashCode *h_contract,
+  const struct TALER_Amount *amount,
+  const struct TALER_Amount *max_fee,
+  const struct TALER_MerchantPublicKeyP *merchant_pub,
+  const struct TALER_MerchantSignatureP *merchant_sig,
+  struct GNUNET_TIME_Absolute timestamp,
+  struct GNUNET_TIME_Absolute refund_deadline,
+  struct GNUNET_TIME_Absolute pay_deadline,
+  const struct GNUNET_HashCode *h_wire,
+  const char *order_id,
+  unsigned int num_coins,
+  const struct TALER_MERCHANT_PayCoin coins[],
+  TALER_MERCHANT_PayCallback pay_cb,
+  void *pay_cb_cls);
+
+
+/**
+ * Cancel a POST /orders/$ID/pay request.  Note that if you cancel a request
+ * like this, you have no assurance that the request has not yet been
+ * forwarded to the merchant. Thus, the payment may still succeed or fail.
+ * Re-issue the original /pay request to resume/retry and obtain a definitive
+ * result, or refresh the coins involved to ensure that the merchant can no
+ * longer complete the payment.
+ *
+ * @param oph the payment request handle
+ */
+void
+TALER_MERCHANT_order_pay_cancel (struct TALER_MERCHANT_OrderPayHandle *oph);
+
+
+/**
+ * Handle for an POST /orders/$ID/abort operation.
+ */
+struct TALER_MERCHANT_AbortHandle;
 
 
 /**
  * Entry in the array of refunded coins.
  */
-struct TALER_MERCHANT_RefundEntry
+struct TALER_MERCHANT_AbortedCoin
 {
   /**
    * Merchant signature affirming the refund.
@@ -1717,28 +1665,31 @@ struct TALER_MERCHANT_RefundEntry
 
 /**
  * Callbacks of this type are used to serve the result of submitting a
- * /pay request to a merchant.
+ * /orders/$ID/abort request to a merchant.
  *
  * @param cls closure
  * @param hr HTTP response details
  * @param merchant_pub public key of the merchant
  * @param h_contract hash of the contract
- * @param num_refunds size of the @a res array, 0 on errors
- * @param res merchant signatures refunding coins, NULL on errors
+ * @param num_aborts size of the @a res array, 0 on errors
+ * @param aborts merchant signatures refunding coins, NULL on errors
  */
 typedef void
-(*TALER_MERCHANT_PayRefundCallback) (
+(*TALER_MERCHANT_PayAbortCallback) (
   void *cls,
   const struct TALER_MERCHANT_HttpResponse *hr,
   const struct TALER_MerchantPublicKeyP *merchant_pub,
   const struct GNUNET_HashCode *h_contract,
-  unsigned int num_refunds,
-  const struct TALER_MERCHANT_RefundEntry *res);
+  unsigned int num_aborts,
+  const struct TALER_MERCHANT_AbortedCoin aborts[]);
 
 
 /**
- * Run a payment abort operation, asking for refunds for coins
- * that were previously spend on a /pay that failed to go through.
+ * Run a payment abort operation, asking for the payment to be aborted,
+ * yieldingrefunds for coins that were previously spend on a payment that
+ * failed to go through.
+ *
+ * This is a PUBLIC API for wallets.
  *
  * @param ctx execution context
  * @param merchant_url base URL of the merchant
@@ -1759,119 +1710,194 @@ typedef void
  * @param payref_cb_cls closure for @a pay_cb
  * @return a handle for this request
  */
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_abort (struct GNUNET_CURL_Context *ctx,
-                          const char *merchant_url,
-                          const struct GNUNET_HashCode *h_contract,
-                          const struct TALER_Amount *amount,
-                          const struct TALER_Amount *max_fee,
-                          const struct TALER_MerchantPublicKeyP *merchant_pub,
-                          const struct TALER_MerchantSignatureP *merchant_sig,
-                          struct GNUNET_TIME_Absolute timestamp,
-                          struct GNUNET_TIME_Absolute refund_deadline,
-                          struct GNUNET_TIME_Absolute pay_deadline,
-                          const struct GNUNET_HashCode *h_wire,
-                          const char *order_id,
-                          unsigned int num_coins,
-                          const struct TALER_MERCHANT_PayCoin *coins,
-                          TALER_MERCHANT_PayRefundCallback payref_cb,
-                          void *payref_cb_cls);
+struct TALER_MERCHANT_AbortHandle *
+TALER_MERCHANT_order_abort (struct GNUNET_CURL_Context *ctx,
+                            const char *merchant_url,
+                            const struct GNUNET_HashCode *h_contract,
+                            const struct TALER_Amount *amount,
+                            const struct TALER_Amount *max_fee,
+                            const struct TALER_MerchantPublicKeyP 
*merchant_pub,
+                            const struct TALER_MerchantSignatureP 
*merchant_sig,
+                            struct GNUNET_TIME_Absolute timestamp,
+                            struct GNUNET_TIME_Absolute refund_deadline,
+                            struct GNUNET_TIME_Absolute pay_deadline,
+                            const struct GNUNET_HashCode *h_wire,
+                            const char *order_id,
+                            unsigned int num_coins,
+                            const struct TALER_MERCHANT_PayCoin coins[],
+                            TALER_MERCHANT_PayAbortCallback cb,
+                            void *cb_cls);
 
 
 /**
- * Information we need from the frontend (ok, the frontend sends just JSON)
- * when forwarding a payment to the backend.
+ * Cancel a POST /orders/$ID/abort request.  Note that if you cancel a request
+ * like this, you have no assurance that the request has not yet been
+ * forwarded to the merchant.
+ *
+ * @param oah the abort request handle
  */
-struct TALER_MERCHANT_PaidCoin
-{
-  /**
-   * Denomination key with which the coin is signed
-   */
-  struct TALER_DenominationPublicKey denom_pub;
+void
+TALER_MERCHANT_order_abort_cancel (struct TALER_MERCHANT_OrderAbortandle *oah);
 
-  /**
-   * Exchange’s unblinded signature of the coin
-   */
-  struct TALER_DenominationSignature denom_sig;
+
+/* *********************   OLD ************************** */
+
+
+/* ********************* /refund ************************** */
+
+/**
+ * Handle for a GET /refund operation.
+ */
+struct TALER_MERCHANT_RefundLookupOperation;
+
+
+/**
+ * Detail about a refund lookup result.
+ */
+struct TALER_MERCHANT_RefundDetail
+{
 
   /**
-   * Overall value that coins of this @e denom_pub have.
+   * Exchange response details.  Full details are only included
+   * upon failure (HTTP status is not #MHD_HTTP_OK).
    */
-  struct TALER_Amount denom_value;
+  struct TALER_EXCHANGE_HttpResponse hr;
 
   /**
-   * Coin's public key.
+   * Coin this detail is about.
    */
   struct TALER_CoinSpendPublicKeyP coin_pub;
 
   /**
-   * Coin's signature key.
+   * Refund transaction ID used.
    */
-  struct TALER_CoinSpendSignatureP coin_sig;
+  uint64_t rtransaction_id;
 
   /**
-   * Amount this coin contributes to (including fee).
+   * Amount to be refunded for this coin.
    */
-  struct TALER_Amount amount_with_fee;
+  struct TALER_Amount refund_amount;
 
   /**
-   * Amount this coin contributes to (without fee).
+   * Applicable refund transaction fee.
    */
-  struct TALER_Amount amount_without_fee;
+  struct TALER_Amount refund_fee;
 
   /**
-   * Fee the exchange charges for refunds of this coin.
+   * Public key of the exchange affirming the refund,
+   * only valid if the @e hr http_status is #MHD_HTTP_OK.
    */
-  struct TALER_Amount refund_fee;
+  struct TALER_ExchangePublicKeyP exchange_pub;
 
   /**
-   * What is the URL of the exchange that issued @a coin_pub?
+   * Signature of the exchange affirming the refund,
+   * only valid if the @e hr http_status is #MHD_HTTP_OK.
    */
-  const char *exchange_url;
+  struct TALER_ExchangeSignatureP exchange_sig;
 
 };
 
 
 /**
- * Pay a merchant.  API for frontends talking to backends. Here,
- * the frontend does not have the coin's private keys, but just
- * the public keys and signatures.  Note the sublte difference
- * in the type of @a coins compared to #TALER_MERCHANT_pay().
+ * Callback to process a GET /refund request
  *
- * @param ctx execution context
- * @param merchant_url base URL of the merchant
+ * @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 order_id which order should be paid
- * @param num_coins length of the @a coins array
- * @param coins array of coins to pay with
- * @param pay_cb the callback to call when a reply for this request is 
available
- * @param pay_cb_cls closure for @a pay_cb
- * @return a handle for this request
+ * @param num_details length of the @a details array
+ * @param details details about the refund processing
  */
-struct TALER_MERCHANT_Pay *
-TALER_MERCHANT_pay_frontend (
-  struct GNUNET_CURL_Context *ctx,
-  const char *merchant_url,
+typedef void
+(*TALER_MERCHANT_RefundLookupCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr,
+  const struct GNUNET_HashCode *h_contract_terms,
   const struct TALER_MerchantPublicKeyP *merchant_pub,
-  const char *order_id,
-  unsigned int num_coins,
-  const struct TALER_MERCHANT_PaidCoin *coins,
-  TALER_MERCHANT_PayCallback pay_cb,
-  void *pay_cb_cls);
+  unsigned int num_details,
+  const struct TALER_MERCHANT_RefundDetail *details);
+
+
+/**
+ * Does a GET /refund.
+ *
+ * @param ctx execution context
+ * @param backend_url base URL of the merchant backend
+ * @param order_id order id used to perform the lookup
+ * @param cb callback which will work the response gotten from the backend
+ * @param cb_cls closure to pass to the callback
+ * @return handle for this operation, NULL upon errors
+ */
+struct TALER_MERCHANT_RefundLookupOperation *
+TALER_MERCHANT_refund_lookup (struct GNUNET_CURL_Context *ctx,
+                              const char *backend_url,
+                              const char *order_id,
+                              TALER_MERCHANT_RefundLookupCallback cb,
+                              void *cb_cls);
+
+/**
+ * Cancel a GET /refund request.
+ *
+ * @param rlo the refund increasing operation to cancel
+ */
+void
+TALER_MERCHANT_refund_lookup_cancel (
+  struct TALER_MERCHANT_RefundLookupOperation *rlo);
+
+
+/**
+ * Handle for a POST /refund operation.
+ */
+struct TALER_MERCHANT_RefundIncreaseOperation;
 
 
 /**
- * Cancel a /pay request.  Note that if you cancel a request like
- * this, you have no assurance that the request has not yet been
- * forwarded to the merchant. Thus, the payment may still succeed or
- * fail.  Re-issue the original /pay request to resume/retry and
- * obtain a definitive result, or /refresh the coins involved to
- * ensure that the merchant can no longer complete the payment.
+ * Callback to process a POST /refund request
  *
- * @param ph the payment request handle
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param ec taler-specific error code
+ */
+typedef void
+(*TALER_MERCHANT_RefundIncreaseCallback) (
+  void *cls,
+  const struct TALER_MERCHANT_HttpResponse *hr);
+
+
+/**
+ * Increase the refund associated to a order
+ *
+ * @param ctx the CURL context used to connect to the backend
+ * @param backend_url backend's base URL, including final "/"
+ * @param order_id id of the order whose refund is to be increased
+ * @param refund amount to which increase the refund
+ * @param reason human-readable reason justifying the refund
+ * @param cb callback processing the response from /refund
+ * @param cb_cls closure for cb
+ */
+struct TALER_MERCHANT_RefundIncreaseOperation *
+TALER_MERCHANT_refund_increase (struct GNUNET_CURL_Context *ctx,
+                                const char *backend_url,
+                                const char *order_id,
+                                const struct TALER_Amount *refund,
+                                const char *reason,
+                                TALER_MERCHANT_RefundIncreaseCallback cb,
+                                void *cb_cls);
+
+/**
+ * Cancel a POST /refund request.
+ *
+ * @param rio the refund increasing operation to cancel
  */
 void
-TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *ph);
+TALER_MERCHANT_refund_increase_cancel (
+  struct TALER_MERCHANT_RefundIncreaseOperation *rio);
+
+
+/* *********************  /proposal *********************** */
+
+
+/* *********************  /pay *********************** */
 
 
 /* ********************* /track/transfer *********************** */
diff --git a/src/include/taler_merchantdb_plugin.h 
b/src/include/taler_merchantdb_plugin.h
index 818e7dd..2ef75de 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -299,6 +299,41 @@ typedef void
                                    struct GNUNET_TIME_Absolute timestamp);
 
 
+/**
+ * Function called with information about a coin that was deposited.
+ *
+ * @param cls closure
+ * @param coin_pub public key of the coin
+ * @param exchange_url URL of the exchange that issued the coin
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param refund_fee fee the exchange will charge for refunding this coin
+ * @param wire_fee wire fee the exchange charges
+ */
+typedef void
+(*TALER_MERCHANTDB_DepositCallback)(
+  void *cls,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_Amount *deposit_fee,
+  const struct TALER_Amount *refund_fee,
+  const struct TALER_Amount *wire_fee);
+
+
+/**
+ * Function called with information about a refund.
+ *
+ * @param cls closure
+ * @param coin_pub public coin from which the refund comes from
+ * @param refund_amount refund amount which is being taken from @a coin_pub
+ */
+typedef void
+(*TALER_MERCHANTDB_RefundCallback)(
+  void *cls,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub,
+  const struct TALER_Amount *refund_amount);
+
+
 /* **************** OLD: ******************** */
 
 /**
@@ -415,7 +450,7 @@ typedef void
  * @param refund_fee cost of this refund operation
  */
 typedef void
-(*TALER_MERCHANTDB_RefundCallback)(
+(*TALER_MERCHANTDB_CoinRefundCallback)(
   void *cls,
   const struct TALER_CoinSpendPublicKeyP *coin_pub,
   const char *exchange_url,
@@ -853,28 +888,91 @@ struct TALER_MERCHANTDB_Plugin
                            const char *order_id,
                            struct GNUNET_TIME_Relative legal_expiration);
 
-  /* ****************** OLD API ******************** */
 
+  /**
+   * Lookup information about coins that were successfully deposited for a
+   * given contract.
+   *
+   * @param cls closure
+   * @param instance_id instance to lookup deposits for
+   * @param h_contract_terms proposal data's hashcode
+   * @param cb function to call with payment data
+   * @param cb_cls closure for @a cb
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_deposits)(void *cls,
+                     const char *instance_id,
+                     const struct GNUNET_HashCode *h_contract_terms,
+                     TALER_MERCHANTDB_DepositCallback cb,
+                     void *cb_cls);
 
   /**
-   * Mark contract terms as paid.  Needed by /history as only paid
-   * contracts must be shown.
+   * Insert payment confirmation from the exchange into the database.
    *
-   * NOTE: we can't get the list of (paid) contracts from the
-   * transactions table because it lacks contract_terms plain JSON.
-   * In facts, the protocol doesn't allow to store contract_terms in
-   * transactions table, as /pay handler doesn't receive this data
-   * (only /proposal does).
+   * @param cls closure
+   * @param instance_id instance to lookup deposits for
+   * @param h_contract_terms proposal data's hashcode
+   * @param coin_pub public key of the coin
+   * @param exchange_url URL of the exchange that issued @a coin_pub
+   * @param amount_with_fee amount the exchange will deposit for this coin
+   * @param deposit_fee fee the exchange will charge for this coin
+   * @param wire_fee wire fee the exchange charges
+   * @param exchange_pub public key used by the exchange for @a exchange_sig
+   * @param exchange_sig signature from exchange that coin was accepted
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*insert_deposit)(void *cls,
+                    const char *instance_id,
+                    const struct GNUNET_HashCode *h_contract_terms,
+                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    const char *exchange_url,
+                    const struct TALER_Amount *amount_with_fee,
+                    const struct TALER_Amount *deposit_fee,
+                    const struct TALER_Amount *refund_fee,
+                    const struct TALER_Amount *wire_fee,
+                    const struct TALER_ExchangeSignatureP *exchange_sig,
+                    const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+  /**
+   * Obtain refunds associated with a contract.
+   *
+   * @param cls closure, typically a connection to the db
+   * @param instance_id instance to lookup refunds for
+   * @param h_contract_terms hash code of the contract
+   * @param rc function to call for each coin on which there is a refund
+   * @param rc_cls closure for @a rc
+   * @return transaction status
+   */
+  enum GNUNET_DB_QueryStatus
+  (*lookup_refunds)(void *cls,
+                    const char *instance_id,
+                    const struct GNUNET_HashCode *h_contract_terms,
+                    TALER_MERCHANTDB_RefundCallback rc,
+                    void *rc_cls);
+
+
+  /**
+   * Mark contract as paid and store the current @a session_id
+   * for which the contract was paid.
    *
    * @param cls closure
+   * @param instance_id instance to mark contract as paid for
    * @param h_contract_terms hash of the contract that is now paid
-   * @param merchant_pub merchant's public key
+   * @param session_id the session that paid the contract, can be NULL
    * @return transaction status
    */
   enum GNUNET_DB_QueryStatus
-  (*mark_proposal_paid)(void *cls,
+  (*mark_contract_paid)(void *cls,
+                        const char *instance_id,
                         const struct GNUNET_HashCode *h_contract_terms,
-                        const struct TALER_MerchantPublicKeyP *merchant_pub);
+                        const char *session_id);
+
+
+  /* ****************** OLD API ******************** */
+
 
   /**
    * Store the order ID that was used to pay for a resource within a session.
@@ -1022,35 +1120,6 @@ struct TALER_MERCHANTDB_Plugin
     void *cb_cls);
 
 
-  /**
-   * Insert payment confirmation from the exchange into the database.
-   *
-   * @param cls closure
-   * @param h_contract_terms proposal data's hashcode
-   * @param merchant_pub merchant's public key
-   * @param coin_pub public key of the coin
-   * @param exchange_url URL of the exchange that issued @a coin_pub
-   * @param amount_with_fee amount the exchange will deposit for this coin
-   * @param deposit_fee fee the exchange will charge for this coin
-   * @param wire_fee wire fee the exchange charges
-   * @param signkey_pub public key used by the exchange for @a exchange_proof
-   * @param exchange_proof proof from exchange that coin was accepted
-   * @return transaction status
-   */
-  enum GNUNET_DB_QueryStatus
-  (*store_deposit)(void *cls,
-                   const struct GNUNET_HashCode *h_contract_terms,
-                   const struct TALER_MerchantPublicKeyP *merchant_pub,
-                   const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                   const char *exchange_url,
-                   const struct TALER_Amount *amount_with_fee,
-                   const struct TALER_Amount *deposit_fee,
-                   const struct TALER_Amount *refund_fee,
-                   const struct TALER_Amount *wire_fee,
-                   const struct TALER_ExchangePublicKeyP *signkey_pub,
-                   const json_t *exchange_proof);
-
-
   /**
    * Insert mapping of @a coin_pub and @a h_contract_terms to
    * corresponding @a wtid.
@@ -1118,25 +1187,6 @@ struct TALER_MERCHANTDB_Plugin
     const struct TALER_MasterSignatureP *exchange_sig);
 
 
-  /**
-   * Lookup information about coin payments by proposal data's hashcode.
-   *
-   * @param cls closure
-   * @param h_contract_terms proposal data's hashcode
-   * @param merchant_pub merchant's public key. It's AND'd with @a 
h_contract_terms
-   *        in order to find the result.
-   * @param cb function to call with payment data
-   * @param cb_cls closure for @a cb
-   * @return transaction status
-   */
-  enum GNUNET_DB_QueryStatus
-  (*find_payments)(void *cls,
-                   const struct GNUNET_HashCode *h_contract_terms,
-                   const struct TALER_MerchantPublicKeyP *merchant_pub,
-                   TALER_MERCHANTDB_CoinDepositCallback cb,
-                   void *cb_cls);
-
-
   /**
    * Lookup information about coin payments by h_contract_terms and coin.
    *
@@ -1270,25 +1320,6 @@ struct TALER_MERCHANTDB_Plugin
     const char *reason);
 
 
-  /**
-   * Obtain refunds associated with a contract.
-   *
-   * @param cls closure, typically a connection to the db
-   * @param merchant_pub public key of the merchant instance
-   * @param h_contract_terms hash code of the contract
-   * @param rc function to call for each coin on which there is a refund
-   * @param rc_cls closure for @a rc
-   * @return transaction status
-   */
-  enum GNUNET_DB_QueryStatus
-  (*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);
-
-
   /**
    * Obtain refund proofs associated with a refund operation on a
    * coin.

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



reply via email to

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