gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] 70/277: work on /pay and /abort processing


From: gnunet
Subject: [taler-merchant] 70/277: work on /pay and /abort processing
Date: Sun, 05 Jul 2020 20:49:43 +0200

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

grothoff pushed a commit to branch master
in repository merchant.

commit 577e344b018cf186e179edf8fa56101659e25307
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Thu Apr 30 21:23:40 2020 +0200

    work on /pay and /abort processing
---
 .../taler-merchant-httpd_post-orders-ID-abort.c    | 2332 +++++---------------
 .../taler-merchant-httpd_post-orders-ID-abort.h    |   31 +-
 .../taler-merchant-httpd_post-orders-ID-pay.c      |   15 +-
 src/backenddb/plugin_merchantdb_postgres.c         |   32 +-
 4 files changed, 633 insertions(+), 1777 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
index 7a1b7fd..604490d 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-abort.c
@@ -16,31 +16,24 @@
   License along with TALER; see the file COPYING.  If not,
   see <http://www.gnu.org/licenses/>
 */
-
 /**
- * @file backend/taler-merchant-httpd_pay.c
- * @brief handling of /pay requests
+ * @file backend/taler-merchant-httpd_post-orders-ID-abort.c
+ * @brief handling of POST /orders/$ID/abort 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"
 
 
 /**
  * How long to wait before giving up processing with the exchange?
  */
-#define PAY_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, \
-                                                    30))
+#define ABORT_TIMEOUT (GNUNET_TIME_relative_multiply 
(GNUNET_TIME_UNIT_SECONDS, \
+                                                      30))
 
 /**
  * How often do we retry the (complex!) database transaction?
@@ -48,26 +41,31 @@
 #define MAX_RETRIES 5
 
 /**
- * Information we keep for an individual call to the /pay handler.
+ * Information we keep for an individual call to the /abort handler.
  */
-struct PayContext;
+struct AbortContext;
 
 /**
- * Information kept during a /pay request for each coin.
+ * Information kept during a /abort request for each coin.
  */
-struct DepositConfirmation
+struct RefundDetails
 {
 
   /**
-   * Reference to the main PayContext
+   * Public key of the coin.
+   */
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+
+  /**
+   * Reference to the main AbortContext
    */
-  struct PayContext *pc;
+  struct AbortContext *ac;
 
   /**
-   * Handle to the deposit operation we are performing for
+   * Handle to the refund operation we are performing for
    * this coin, NULL after the operation is done.
    */
-  struct TALER_EXCHANGE_DepositHandle *dh;
+  struct TALER_EXCHANGE_RefundHandle *rh;
 
   /**
    * URL of the exchange that issued this coin.
@@ -75,9 +73,10 @@ struct DepositConfirmation
   char *exchange_url;
 
   /**
-   * Denomination of this coin.
+   * Body of the response from the exchange.  Note that the body returned MUST
+   * be freed (if non-NULL).
    */
-  struct TALER_DenominationPublicKey denom;
+  json_t *exchange_reply;
 
   /**
    * Amount this coin contributes to the total purchase price.
@@ -85,107 +84,69 @@ struct DepositConfirmation
    */
   struct TALER_Amount amount_with_fee;
 
-  /**
-   * Fee charged by the exchange for the deposit operation of this coin.
-   */
-  struct TALER_Amount deposit_fee;
-
   /**
    * Fee charged by the exchange for the refund operation of this coin.
    */
   struct TALER_Amount refund_fee;
 
   /**
-   * Wire fee charged by the exchange of this coin.
-   */
-  struct TALER_Amount wire_fee;
-
-  /**
-   * Public key of the coin.
-   */
-  struct TALER_CoinSpendPublicKeyP coin_pub;
-
-  /**
-   * Signature using the @e denom key over the @e coin_pub.
-   */
-  struct TALER_DenominationSignature ub_sig;
-
-  /**
-   * Signature of the coin's private key over the contract.
-   */
-  struct TALER_CoinSpendSignatureP coin_sig;
-
-  /**
-   * Offset of this coin into the `dc` array of all coins in the
-   * @e pc.
+   * Offset of this coin into the `rd` array of all coins in the
+   * @e ac.
    */
   unsigned int index;
 
   /**
-   * #GNUNET_YES if we found this coin in the database.
+   * HTTP status returned by the exchange (if any).
    */
-  int found_in_db;
+  unsigned int http_status;
 
   /**
-   * #GNUNET_YES if this coin was refunded.
+   * Did we try to process this refund yet?
    */
-  int refunded;
+  boolean processed;
 
 };
 
 
 /**
- * Information we keep for an individual call to the /pay handler.
+ * Information we keep for an individual call to the /abort handler.
  */
-struct PayContext
+struct AbortContext
 {
 
   /**
-   * This field MUST be first for handle_mhd_completion_callback() to work
-   * when it treats this struct as a `struct TM_HandlerContext`.
+   * Hashed contract terms (according to client).
+   */
+  struct GNUNET_HashCode h_contract_terms;
+
+  /**
+   * Context for our operation.
    */
-  struct TM_HandlerContext hc;
+  struct TMH_HandlerContext *hc;
 
   /**
    * Stored in a DLL.
    */
-  struct PayContext *next;
+  struct AbortContext *next;
 
   /**
    * Stored in a DLL.
    */
-  struct PayContext *prev;
+  struct AbortContext *prev;
 
   /**
    * Array with @e coins_cnt coins we are despositing.
    */
-  struct DepositConfirmation *dc;
+  struct RefundDetails *rd;
 
   /**
    * MHD connection to return to
    */
   struct MHD_Connection *connection;
 
-  /**
-   * Instance of the payment's instance (in JSON format)
-   */
-  struct MerchantInstance *mi;
-
-  /**
-   * 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;
-
   /**
    * Task called when the (suspended) processing for
-   * the /pay request times out.
+   * the /abort request times out.
    * Happens when we don't get a response from the exchange.
    */
   struct GNUNET_SCHEDULER_Task *timeout_task;
@@ -196,7 +157,7 @@ struct PayContext
   struct MHD_Response *response;
 
   /**
-   * Handle to the exchange that we are doing the payment with.
+   * Handle to the exchange that we are doing the abortment with.
    * (initially NULL while @e fo is trying to find a exchange).
    */
   struct TALER_EXCHANGE_Handle *mh;
@@ -214,124 +175,7 @@ struct PayContext
   const char *current_exchange;
 
   /**
-   * Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
-   */
-  void *json_parse_context;
-
-  /**
-   * Optional session id given in @e root.
-   * NULL if not given.
-   */
-  char *session_id;
-
-  /**
-   * Transaction ID given in @e root.
-   */
-  char *order_id;
-
-  /**
-   * Fulfillment URL from @e contract_terms.
-   */
-  char *fulfillment_url;
-
-  /**
-   * Hashed proposal.
-   */
-  struct GNUNET_HashCode h_contract_terms;
-
-  /**
-   * "h_wire" from @e contract_terms.  Used to identify
-   * the instance's wire transfer method.
-   */
-  struct GNUNET_HashCode h_wire;
-
-  /**
-   * Maximum fee the merchant is willing to pay, from @e root.
-   * Note that IF the total fee of the exchange is higher, that is
-   * acceptable to the merchant if the customer is willing to
-   * pay the difference
-   * (i.e. amount - max_fee <= actual-amount - actual-fee).
-   */
-  struct TALER_Amount max_fee;
-
-  /**
-   * Maximum wire fee the merchant is willing to pay, from @e root.
-   * Note that IF the total fee of the exchange is higher, that is
-   * acceptable to the merchant if the customer is willing to
-   * pay the amorized difference.  Wire fees are charged over an
-   * aggregate of several translations, hence unlike the deposit
-   * fees, they are amortized over several customer's transactions.
-   * The contract specifies under @e wire_fee_amortization how many
-   * customer's transactions he expects the wire fees to be amortized
-   * over on average.  Thus, if the wire fees are larger than
-   * @e max_wire_fee, each customer is expected to contribute
-   * $\frac{actual-wire-fee - max_wire_fee}{wire_fee_amortization}$.
-   * The customer's contribution may be further reduced by the
-   * difference between @e max_fee and the sum of the deposit fees.
-   *
-   * Default is that the merchant is unwilling to pay any wire fees.
-   */
-  struct TALER_Amount max_wire_fee;
-
-  /**
-   * Amount from @e root.  This is the amount the merchant expects
-   * to make, minus @e max_fee.
-   */
-  struct TALER_Amount amount;
-
-  /**
-   * Considering all the coins with the "found_in_db" flag
-   * set, what is the total amount we were so far paid on
-   * this contract?
-   */
-  struct TALER_Amount total_paid;
-
-  /**
-   * Considering all the coins with the "found_in_db" flag
-   * set, what is the total amount we had to pay in deposit
-   * fees so far on this contract?
-   */
-  struct TALER_Amount total_fees_paid;
-
-  /**
-   * Considering all the coins with the "found_in_db" flag
-   * set, what is the total amount we already refunded?
-   */
-  struct TALER_Amount total_refunded;
-
-  /**
-   * Wire transfer deadline. How soon would the merchant like the
-   * wire transfer to be executed?
-   */
-  struct GNUNET_TIME_Absolute wire_transfer_deadline;
-
-  /**
-   * Timestamp from @e contract_terms.
-   */
-  struct GNUNET_TIME_Absolute timestamp;
-
-  /**
-   * Refund deadline from @e contract_terms.
-   */
-  struct GNUNET_TIME_Absolute refund_deadline;
-
-  /**
-   * Deadline for the customer to pay for this proposal.
-   */
-  struct GNUNET_TIME_Absolute pay_deadline;
-
-  /**
-   * Number of transactions that the wire fees are expected to be
-   * amortized over.  Never zero, defaults (conservateively) to 1.
-   * May be higher if merchants expect many small transactions to
-   * be aggregated and thus wire fees to be reasonably amortized
-   * due to aggregation.
-   */
-  uint32_t wire_fee_amortization;
-
-  /**
-   * Number of coins this payment is made of.  Length
-   * of the @e dc array.
+   * Number of coins this abort is for.  Length of the @e rd array.
    */
   unsigned int coins_cnt;
 
@@ -366,537 +210,220 @@ struct PayContext
    * #GNUNET_NO if the @e connection was not suspended,
    * #GNUNET_YES if the @e connection was suspended,
    * #GNUNET_SYSERR if @e connection was resumed to as
-   * part of #MH_force_pc_resume during shutdown.
+   * part of #MH_force_ac_resume during shutdown.
    */
   int suspended;
 
-  /**
-   * #GNUNET_YES if we already tried a forced /keys download.
-   */
-  int tried_force_keys;
-
-  /**
-   * Which operational mode is the /pay request made in?
-   */
-  enum { PC_MODE_PAY, PC_MODE_ABORT_REFUND } mode;
-
 };
 
 
 /**
- * Head of active pay context DLL.
+ * Head of active abort context DLL.
  */
-static struct PayContext *pc_head;
+static struct AbortContext *ac_head;
 
 /**
- * Tail of active pay context DLL.
+ * Tail of active abort context DLL.
  */
-static struct PayContext *pc_tail;
+static struct AbortContext *ac_tail;
 
 
 /**
  * Abort all pending /deposit operations.
  *
- * @param pc pay context to abort
+ * @param ac abort context to abort
  */
 static void
-abort_deposit (struct PayContext *pc)
+abort_refunds (struct AbortContext *ac)
 {
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Aborting pending /deposit operations\n");
-  for (unsigned int i = 0; i<pc->coins_cnt; i++)
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
   {
-    struct DepositConfirmation *dci = &pc->dc[i];
+    struct RefundDetails *rdi = &ac->rd[i];
 
-    if (NULL != dci->dh)
+    if (NULL != rdi->dh)
     {
-      TALER_EXCHANGE_deposit_cancel (dci->dh);
-      dci->dh = NULL;
+      TALER_EXCHANGE_refund_cancel (rdi->rh);
+      rdi->rh = NULL;
     }
   }
 }
 
 
 /**
- * Force all pay contexts to be resumed as we are about
+ * Force all abort contexts to be resumed as we are about
  * to shut down MHD.
  */
 void
-MH_force_pc_resume ()
+TMH_force_ac_resume ()
 {
-  for (struct PayContext *pc = pc_head;
-       NULL != pc;
-       pc = pc->next)
+  for (struct AbortContext *ac = ac_head;
+       NULL != ac;
+       ac = ac->next)
   {
-    abort_deposit (pc);
-    if (NULL != pc->timeout_task)
+    abort_deposit (ac);
+    if (NULL != ac->timeout_task)
     {
-      GNUNET_SCHEDULER_cancel (pc->timeout_task);
-      pc->timeout_task = NULL;
+      GNUNET_SCHEDULER_cancel (ac->timeout_task);
+      ac->timeout_task = NULL;
     }
-    if (GNUNET_YES == pc->suspended)
+    if (GNUNET_YES == ac->suspended)
     {
-      pc->suspended = GNUNET_SYSERR;
-      MHD_resume_connection (pc->connection);
+      ac->suspended = GNUNET_SYSERR;
+      MHD_resume_connection (ac->connection);
     }
   }
 }
 
 
 /**
- * Resume the given pay context and send the given response.
- * Stores the response in the @a pc and signals MHD to resume
+ * Resume the given abort context and send the given response.
+ * Stores the response in the @a ac and signals MHD to resume
  * the connection.  Also ensures MHD runs immediately.
  *
- * @param pc payment context
+ * @param ac abortment context
  * @param response_code response code to use
  * @param response response data to send back
  */
 static void
-resume_pay_with_response (struct PayContext *pc,
-                          unsigned int response_code,
-                          struct MHD_Response *response)
+resume_abort_with_response (struct AbortContext *ac,
+                            unsigned int response_code,
+                            struct MHD_Response *response)
 {
-  pc->response_code = response_code;
-  pc->response = response;
+  abort_refunds (ac);
+  ac->response_code = response_code;
+  ac->response = response;
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Resuming /pay handling as exchange interaction is done (%u)\n",
+              "Resuming /abort handling as exchange interaction is done 
(%u)\n",
               response_code);
-  if (NULL != pc->timeout_task)
+  if (NULL != ac->timeout_task)
   {
-    GNUNET_SCHEDULER_cancel (pc->timeout_task);
-    pc->timeout_task = NULL;
+    GNUNET_SCHEDULER_cancel (ac->timeout_task);
+    ac->timeout_task = NULL;
   }
-  GNUNET_assert (GNUNET_YES == pc->suspended);
-  pc->suspended = GNUNET_NO;
-  MHD_resume_connection (pc->connection);
+  GNUNET_assert (GNUNET_YES == ac->suspended);
+  ac->suspended = GNUNET_NO;
+  MHD_resume_connection (ac->connection);
   TMH_trigger_daemon (); /* we resumed, kick MHD */
 }
 
 
 /**
- * Resume payment processing with an error.
+ * Resume abortment processing with an error.
  *
- * @param pc operation to resume
+ * @param ac operation to resume
  * @param http_status http status code to return
  * @param ec taler error code to return
  * @param msg human readable error message
  */
 static void
-resume_pay_with_error (struct PayContext *pc,
-                       unsigned int http_status,
-                       enum TALER_ErrorCode ec,
-                       const char *msg)
+resume_abort_with_error (struct AbortContext *ac,
+                         unsigned int http_status,
+                         enum TALER_ErrorCode ec,
+                         const char *msg)
 {
-  resume_pay_with_response (pc,
-                            http_status,
-                            TALER_MHD_make_error (ec,
-                                                  msg));
+  resume_abort_with_response (ac,
+                              http_status,
+                              TALER_MHD_make_error (ec,
+                                                    msg));
 }
 
 
 /**
- * Generate a response that indicates payment success.
+ * Generate a response that indicates abortment success.
  *
- * @param pc payment context
+ * @param ac abortment context
  */
 static void
-generate_success_response (struct PayContext *pc)
+generate_success_response (struct AbortContext *ac)
 {
   json_t *refunds;
-  struct GNUNET_CRYPTO_EddsaSignature sig;
 
-  /* Check for applicable refunds */
+  refunds = json_array ();
+  if (NULL == 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,
+    GNUNET_break (0);
+    resume_abort_with_error (ac,
                              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);
+                             TALER_EC_JSON_ALLOCATION_FAILURE,
+                             "could not create JSON array");
+    return;
   }
-
-  /* Build the response */
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
   {
-    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)
+    struct RefundDetails *rdi = &ac->rd[i];
+
+    if (0 !=
+        json_array_append_new (
+          refunds,
+          json_pack ("{s:I, s:O, s:o s:o s:o}",
+                     "exchange_status",
+                     (json_int_t) rdi->exchange_status,
+                     "exchange_body",
+                     rdi->exchange_body)))
     {
+      json_decref (refunds);
       GNUNET_break (0);
-      resume_pay_with_error (pc,
-                             MHD_HTTP_INTERNAL_SERVER_ERROR,
-                             TALER_EC_JSON_ALLOCATION_FAILURE,
-                             "could not build final response");
+      resume_abort_with_error (ac,
+                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                               TALER_EC_JSON_ALLOCATION_FAILURE,
+                               "could not create JSON array");
       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.
- */
-static void
-pay_context_cleanup (struct TM_HandlerContext *hc)
-{
-  struct PayContext *pc = (struct PayContext *) hc;
-
-  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);
-      dc->ub_sig.rsa_signature = NULL;
-    }
-    GNUNET_free_non_null (dc->exchange_url);
-  }
-  GNUNET_free_non_null (pc->dc);
-  if (NULL != pc->fo)
-  {
-    TMH_EXCHANGES_find_exchange_cancel (pc->fo);
-    pc->fo = NULL;
-  }
-  if (NULL != pc->response)
-  {
-    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);
-  GNUNET_free (pc);
+  /* Resume and send back the response.  */
+  resume_abort_with_response (ac,
+                              MHD_HTTP_OK,
+                              TALER_MHD_make_json_pack ("{s:o}",
+                                                        "refunds",
+                                                        refunds));
 }
 
 
 /**
- * Check whether the amount paid is sufficient to cover
- * the contract.
+ * Custom cleanup routine for a `struct AbortContext`.
  *
- * @param pc payment context to check
- * @return #GNUNET_OK if the payment is sufficient, #GNUNET_SYSERR if it is
- *         insufficient
+ * @param hc the `struct AbortContext` to clean up.
  */
-static int
-check_payment_sufficient (struct PayContext *pc)
+static void
+abort_context_cleanup (struct TM_HandlerContext *hc)
 {
-  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 (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;
-
-  /**
-   * 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++)
-  {
-    struct DepositConfirmation *dc = &pc->dc[i];
-
-    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)) )
-    {
-      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");
-    }
-    if (1 ==
-        TALER_amount_cmp (&dc->deposit_fee,
-                          &dc->amount_with_fee))
-    {
-      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;
-    }
-
-    /* If exchange differs, add wire fee */
-    {
-      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;
-        }
-      }
-    }
-  }
+  struct AbortContext *ac = (struct AbortContext *) hc;
 
-  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))
+  if (NULL != ac->timeout_task)
   {
-    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;
+    GNUNET_SCHEDULER_cancel (ac->timeout_task);
+    ac->timeout_task = NULL;
   }
-
-  switch (TALER_amount_subtract (&wire_fee_delta,
-                                 &total_wire_fee,
-                                 &pc->max_wire_fee))
+  abort_refunds (ac);
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
   {
-  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);
-  }
+    struct RefundDetails *rd = &ac->rd[i];
 
-  /* 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))
+    if (NULL != rdi->exchange_reply)
     {
-      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;
+      json_decref (rdi->exchange_reply);
+      rdi->reply = NULL;
     }
+    GNUNET_free_non_null (rd->exchange_url);
   }
-  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_free_non_null (ac->rd);
+  if (NULL != ac->fo)
   {
-    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;
+    TMH_EXCHANGES_find_exchange_cancel (ac->fo);
+    ac->fo = NULL;
   }
-
-  if (-1 == TALER_amount_cmp (&final_amount,
-                              &total_needed))
+  if (NULL != ac->response)
   {
-    /* 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;
+    MHD_destroy_response (ac->response);
+    ac->response = NULL;
   }
-
-
-  return GNUNET_OK;
+  GNUNET_CONTAINER_DLL_remove (ac_head,
+                               ac_tail,
+                               ac);
+  GNUNET_free (ac);
 }
 
 
@@ -904,196 +431,61 @@ check_payment_sufficient (struct PayContext *pc)
  * 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
+ * @param ac abortment context we are processing
  */
 static void
-begin_transaction (struct PayContext *pc);
+find_next_exchange (struct AbortContext *ac);
 
 
 /**
- * Callback to handle a deposit permission's response.
+ * Function called with the result from the exchange (to be
+ * passed back to the wallet).
  *
- * @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
+ * @param cls closure
+ * @param hr HTTP response data
+ * @param sign_key exchange key used to sign @a obj, or NULL
+ * @param signature the actual signature, or NULL on error
  */
 static void
-deposit_cb (void *cls,
-            const struct TALER_EXCHANGE_HttpResponse *hr,
-            const struct TALER_ExchangeSignatureP *exchange_sig,
-            const struct TALER_ExchangePublicKeyP *sign_key)
+refund_cb (void *cls,
+           const struct TALER_EXCHANGE_HttpResponse *hr,
+           const struct TALER_ExchangePublicKeyP *sign_key,
+           const struct TALER_ExchangeSignatureP *signature)
 {
-  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",
-              GNUNET_h2s (&pc->h_contract_terms),
-              TALER_B2S (&pc->mi->pubkey));
-  /* 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);
-  if (0 > qs)
-  {
-    /* Special report if retries insufficient */
-    abort_deposit (pc);
-    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);
-    /* Forward error including 'proof' for the body */
-    resume_pay_with_error (pc,
-                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                           TALER_EC_PAY_DB_STORE_PAY_ERROR,
-                           "Merchant database error");
-    return;
-  }
-  dc->found_in_db = GNUNET_YES;
-  pc->pending--;
-
-  if (0 != pc->pending_at_ce)
-    return; /* still more to do with current exchange */
-  find_next_exchange (pc);
+  struct RefundDetails *rd = cls;
+  struct AbortContext *ac = rd->ac;
+
+  (void) sign_key;
+  (void) signature;
+  rd->rh = NULL;
+  rd.http_status = hr->status;
+  rd.exchange_reply = json_incref ((json_t*) hr->reply);
+  ac->pending_at_ce--;
+  if (0 == ac->pending_at_ce)
+    find_next_exchange (ac);
 }
 
 
 /**
  * Function called with the result of our exchange lookup.
  *
- * @param cls the `struct PayContext`
+ * @param cls the `struct AbortContext`
  * @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
  */
 static void
-process_pay_with_exchange (void *cls,
-                           const struct TALER_EXCHANGE_HttpResponse *hr,
-                           struct TALER_EXCHANGE_Handle *mh,
-                           const struct TALER_Amount *wire_fee,
-                           int exchange_trusted)
+process_abort_with_exchange (void *cls,
+                             const struct TALER_EXCHANGE_HttpResponse *hr,
+                             struct TALER_EXCHANGE_Handle *exchange_handle,
+                             const struct TALER_Amount *wire_fee,
+                             int exchange_trusted)
 {
-  struct PayContext *pc = cls;
-  const struct TALER_EXCHANGE_Keys *keys;
+  struct AbortContext *ac = cls;
+  struct TMH_HandlerContext *hc = pc->hc;
 
   pc->fo = NULL;
   GNUNET_assert (GNUNET_YES == pc->suspended);
@@ -1111,7 +503,7 @@ process_pay_with_exchange (void *cls,
         "hint",
         "failed to obtain meta-data from exchange",
         "code",
-        (json_int_t) TALER_EC_PAY_EXCHANGE_KEYS_FAILURE,
+        (json_int_t) TALER_EC_ABORT_EXCHANGE_KEYS_FAILURE,
         "exchange_http_status",
         (json_int_t) hr->http_status,
         "exchange_code",
@@ -1120,206 +512,93 @@ process_pay_with_exchange (void *cls,
         hr->reply));
     return;
   }
-  pc->mh = mh;
-  keys = TALER_EXCHANGE_get_keys (mh);
-  if (NULL == keys)
-  {
-    GNUNET_break (0); /* should not be possible if HTTP status is #MHD_HTTP_OK 
*/
-    resume_pay_with_error (pc,
-                           MHD_HTTP_FAILED_DEPENDENCY,
-                           TALER_EC_PAY_EXCHANGE_KEYS_FAILURE,
-                           "no keys");
-    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
+  /* Initiate refund operation for all coins of
      the current exchange (!) */
-  GNUNET_assert (0 == pc->pending_at_ce);
-  for (unsigned int i = 0; i<pc->coins_cnt; i++)
+  GNUNET_assert (0 == ac->pending_at_ce);
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
   {
-    struct DepositConfirmation *dc = &pc->dc[i];
-    const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
-    enum TALER_ErrorCode ec;
-    unsigned int hc;
-
-    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)
+    struct RefundDetails *rdi = &ac->rd[i];
+
+    if (rdi->processed)
       continue;
-    if (0 != strcmp (dc->exchange_url,
-                     pc->current_exchange))
+    GNUNET_assert (NULL == rdi->rh);
+    if (0 != strcmp (rdi->exchange_url,
+                     ac->current_exchange))
       continue;
-    denom_details = TALER_EXCHANGE_get_denomination_key (keys,
-                                                         &dc->denom);
-    if (NULL == denom_details)
+    rdi->processed = true;
+    ac->pending--;
+    rdi->rh = TALER_EXCHANGE_refund (exchange_handle,
+                                     &rdi->amount_with_fee,
+                                     &rdi->refund_fee,
+                                     &ac->h_contract_terms,
+                                     &rdi->coin_pub,
+                                     0, /* rtransaction_id */
+                                     &ac->hc->instance->merchant_priv,
+                                     &refund_Cb,
+                                     rdi);
+    if (NULL == rdi->rh)
     {
-      struct GNUNET_HashCode h_denom;
-
-      if (! pc->tried_force_keys)
-      {
-        /* let's try *forcing* a re-download of /keys from the exchange.
-           Maybe the wallet has seen /keys that we missed. */
-        pc->tried_force_keys = GNUNET_YES;
-        pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
-                                              pc->wm->wire_method,
-                                              GNUNET_YES,
-                                              &process_pay_with_exchange,
-                                              pc);
-        if (NULL != pc->fo)
-          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,
-        TALER_MHD_make_json_pack (
-          "{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)));
-      return;
-    }
-    if (GNUNET_OK !=
-        TMH_AUDITORS_check_dk (mh,
-                               denom_details,
-                               exchange_trusted,
-                               &hc,
-                               &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)));
-      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,
-                                     &dc->amount_with_fee,
-                                     pc->wire_transfer_deadline,
-                                     pc->wm->j_wire,
-                                     &pc->h_contract_terms,
-                                     &dc->coin_pub,
-                                     &dc->ub_sig,
-                                     &dc->denom,
-                                     pc->timestamp,
-                                     &pc->mi->pubkey,
-                                     pc->refund_deadline,
-                                     &dc->coin_sig,
-                                     &deposit_cb,
-                                     dc);
-    if (NULL == dc->dh)
-    {
-      /* Signature was invalid or some other constraint was not satisfied.  If
-         the exchange was unavailable, we'd get that information in the
-         callback. */
       GNUNET_break_op (0);
-      resume_pay_with_response (
-        pc,
-        MHD_HTTP_UNAUTHORIZED,
-        TALER_MHD_make_json_pack (
-          "{s:s, s:I, s:i}",
-          "hint", "deposit signature invalid",
-          "code", (json_int_t) TALER_EC_PAY_COIN_SIGNATURE_INVALID,
-          "coin_idx", i));
+      resume_abort_with_error (ac,
+                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                               TALER_EC_ABORT_EXCHANGE_REFUND_FAILED,
+                               "Failed to start refund with exchange");
       return;
     }
-    if (TMH_force_audit)
-      TALER_EXCHANGE_deposit_force_dc (dc->dh);
     pc->pending_at_ce++;
   }
 }
 
 
 /**
- * Find the exchange we need to talk to for the next
- * pending deposit permission.
- *
- * @param pc payment context we are processing
+ * Begin of the DB transaction.  If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
+ *
+ * @param ac abortment context to transact
+ */
+static void
+begin_transaction (struct AbortContext *ac);
+
+
+/**
+ * Find the exchange we need to talk to for the next
+ * pending deposit permission.
+ *
+ * @param ac abortment context we are processing
  */
 static void
-find_next_exchange (struct PayContext *pc)
+find_next_exchange (struct AbortContext *ac)
 {
-  for (unsigned int i = 0; i<pc->coins_cnt; i++)
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
   {
-    struct DepositConfirmation *dc = &pc->dc[i];
+    struct RefundDetails *rdi = &ac->rd[i];
 
-    if (GNUNET_YES != dc->found_in_db)
+    if (! rdi->processed)
     {
-      db->preflight (db->cls);
-      pc->current_exchange = dc->exchange_url;
-      pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
-                                            pc->wm->wire_method,
+      ac->current_exchange = rd->exchange_url;
+      ac->fo = TMH_EXCHANGES_find_exchange (ac->current_exchange,
+                                            ac->wm->wire_method,
                                             GNUNET_NO,
-                                            &process_pay_with_exchange,
-                                            pc);
-      if (NULL == pc->fo)
+                                            &process_abort_with_exchange,
+                                            ac);
+      if (NULL == ac->fo)
       {
         GNUNET_break (0);
-        resume_pay_with_error (pc,
-                               MHD_HTTP_INTERNAL_SERVER_ERROR,
-                               TALER_EC_PAY_EXCHANGE_LOOKUP_FAILED,
-                               "Failed to lookup exchange by URL");
+        resume_abort_with_error (ac,
+                                 MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                 TALER_EC_ABORT_EXCHANGE_LOOKUP_FAILED,
+                                 "Failed to lookup exchange by URL");
         return;
       }
       return;
     }
   }
-  pc->current_exchange = NULL;
-  db->preflight (db->cls);
+  ac->current_exchange = NULL;
+  GNUNET_assert (0 == ac->pending);
   /* We are done with all the HTTP requests, go back and try
      the 'big' database transaction! (It should work now!) */
-  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");
+  begin_transaction (ac);
 }
 
 
@@ -1337,919 +616,476 @@ handle_pay_timeout (void *cls)
  * @param exchange_proof proof from exchange that coin was accepted
  */
 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)
+refund_coins (void *cls,
+              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)
 {
-  struct PayContext *pc = cls;
+  struct AbortContext *ac = cls;
 
-  if (0 != GNUNET_memcmp (&pc->h_contract_terms,
-                          h_contract_terms))
+  for (unsigned int i = 0; i<ac->coins_cnt; i++)
   {
-    GNUNET_break (0);
-    return;
-  }
-  for (unsigned int i = 0; i<pc->coins_cnt; i++)
-  {
-    struct DepositConfirmation *dc = &pc->dc[i];
+    struct RefundDetails *rdi = &ac->rd[i];
 
-    if (GNUNET_YES == dc->found_in_db)
-      continue; /* processed earlier */
+    if ( (0 !=
+          GNUNET_memcmp (coin_pub,
+                         &rdi->coin_pub)) ||
+         (0 !=
+          strcmp (exchange_url,
+                  rdi->exchange_url)) )
+      continue; /* not in request */
 
-    /* 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;
-    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;
-    }
-    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;
-    pc->pending--;
-  }
-}
-
-
-/**
- * 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 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)
- */
-static enum GNUNET_GenericReturnValue
-parse_pay (struct MHD_Connection *connection,
-           const json_t *root,
-           struct PayContext *pc)
-{
-  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;
-
-  res = TALER_MHD_parse_json_data (connection,
-                                   root,
-                                   spec);
-  if (GNUNET_YES != res)
-  {
-    GNUNET_break_op (0);
-    return res;
-  }
-
-  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;
-  }
-
-  {
-    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));
-
-  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;
-  {
-    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 ()
-    };
-
-    res = TALER_MHD_parse_json_data (connection,
-                                     pc->contract_terms,
-                                     espec);
-    if (GNUNET_YES != res)
-    {
-      GNUNET_break (0);
-      GNUNET_JSON_parse_free (spec);
-      return res;
-    }
-
-    pc->fulfillment_url = GNUNET_strdup (fulfillment_url);
-    if (pc->wire_transfer_deadline.abs_value_us <
-        pc->refund_deadline.abs_value_us)
-    {
-      /* 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");
-    }
-
-    if (pc->pay_deadline.abs_value_us <
-        GNUNET_TIME_absolute_get ().abs_value_us)
-    {
-      /* 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;
-    }
-
-  }
-
-  /* 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");
-    }
-    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 ()
-    };
-
-    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;
-    }
-  }
-  else
-  {
-    /* default is we cover no fee */
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_get_zero (pc->max_fee.currency,
-                                          &pc->max_wire_fee));
-  }
-
-  if (NULL != json_object_get (pc->contract_terms,
-                               "wire_fee_amortization"))
-  {
-    struct GNUNET_JSON_Specification espec[] = {
-      GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
-                               &pc->wire_fee_amortization),
-      GNUNET_JSON_spec_end ()
-    };
-
-    res = TALER_MHD_parse_json_data (connection,
-                                     pc->contract_terms,
-                                     espec);
-    if ( (GNUNET_YES != res) ||
-         (0 == pc->wire_fee_amortization) )
-    {
-      GNUNET_break_op (0); /* invalid input, use default */
-      /* default is no amortization */
-      pc->wire_fee_amortization = 1;
-    }
-  }
-  else
-  {
-    pc->wire_fee_amortization = 1;
-  }
-
-  pc->coins_cnt = json_array_size (coins);
-  if (0 == pc->coins_cnt)
-  {
-    GNUNET_JSON_parse_free (spec);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_BAD_REQUEST,
-                                       TALER_EC_PAY_COINS_ARRAY_EMPTY,
-                                       "coins");
-  }
-  /* 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)
+    /* Store refund in DB */
+    qs = TMH_db->refund_coin (TMH_db->cls,
+                              ac->hc->instance->settings.id,
+                              &ac->h_contract_terms,
+                              coin_pub,
+                              /* justification */
+                              "incomplete abortment aborted");
+    if (0 > qs)
     {
-      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)
+      TMH_db->rollback (TMH_db->cls);
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
       {
-        GNUNET_JSON_parse_free (spec);
-        GNUNET_break_op (0);
-        return res;
+        begin_transaction (ac);
+        return;
       }
-      dc->exchange_url = GNUNET_strdup (exchange_url);
-      dc->index = coins_index;
-      dc->pc = pc;
-    }
-  }
-  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))
-    {
-      dc->refunded = GNUNET_YES;
-      GNUNET_assert (0 <=
-                     TALER_amount_add (&pc->total_refunded,
-                                       &pc->total_refunded,
-                                       refund_amount));
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      resume_abort_with_error (ac,
+                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                               TALER_EC_ABORT_DB_STORE_ABORT_ERROR,
+                               "Merchant database error storing abort-refund");
+      return;
     }
-  }
+  } /* for all coins */
 }
 
 
 /**
- * Begin of the DB transaction.  If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
+ * Begin of the DB transaction.  If required (from soft/serialization errors),
+ * the transaction can be restarted here.
  *
- * @param pc payment context to transact
+ * @param ac abortment context to transact
  */
 static void
-begin_transaction (struct PayContext *pc)
+begin_transaction (struct AbortContext *ac)
 {
   enum GNUNET_DB_QueryStatus qs;
 
   /* Avoid re-trying transactions on soft errors forever! */
-  if (pc->retry_counter++ > MAX_RETRIES)
+  if (ac->retry_counter++ > MAX_RETRIES)
   {
     GNUNET_break (0);
-    resume_pay_with_error (pc,
-                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                           TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
-                           "Soft merchant database error: retry counter 
exceeded");
+    resume_abort_with_error (ac,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_ABORT_DB_STORE_TRANSACTION_ERROR,
+                             "Soft merchant database error: retry counter 
exceeded");
     return;
   }
-  GNUNET_assert (GNUNET_YES == pc->suspended);
-
-  /* Init. some price accumulators.  */
-  GNUNET_break (GNUNET_OK ==
-                TALER_amount_get_zero (pc->amount.currency,
-                                       &pc->total_paid));
-  GNUNET_break (GNUNET_OK ==
-                TALER_amount_get_zero (pc->amount.currency,
-                                       &pc->total_fees_paid));
-  GNUNET_break (GNUNET_OK ==
-                TALER_amount_get_zero (pc->amount.currency,
-                                       &pc->total_refunded));
+  GNUNET_assert (GNUNET_YES == ac->suspended);
 
   /* 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 abort"))
   {
     GNUNET_break (0);
-    resume_pay_with_error (pc,
-                           MHD_HTTP_INTERNAL_SERVER_ERROR,
-                           TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
-                           "Merchant database error (could not begin 
transaction)");
+    resume_abort_with_error (ac,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_ABORT_DB_FETCH_TRANSACTION_ERROR,
+                             "Merchant database error (could not begin 
transaction)");
     return;
   }
 
-  /* 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);
+  /* check payment was indeed incomplete */
+  qs = TMH_db->lookup_paid_order (TMH_db->cls,
+                                  ac->hc->instance->settings.id,
+                                  &ac->h_contract_terms,
+                                  NULL);
+  if (0 < qs)
+  {
+    /* Payment is complete, refuse to abort. */
+    json_decref (terms);
+    TMH_db->rollback (TMH_db->cls);
+    resume_abort_with_error (ac,
+                             MHD_HTTP_FORBIDDEN,
+                             
TALER_EC_ABORT_ABORT_REFUND_REFUSED_ABORTMENT_COMPLETE,
+                             "Payment was complete, refusing to abort");
+    return;
+  }
   if (0 > qs)
   {
-    db->rollback (db->cls);
+    TMH_db->rollback (TMH_db->cls);
     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
-      begin_transaction (pc);
+      begin_transaction (ac);
       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_FETCH_TRANSACTION_ERROR,
-                           "Merchant database error");
+    resume_abort_with_error (ac,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_ABORT_DB_STORE_ABORT_ERROR,
+                             "Merchant database error");
     return;
   }
 
-  /* 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);
+  /* Mark all deposits we have in our database for the order as refunded. */
+  qs = TMH_db->lookup_deposits (TMH_db->cls,
+                                ac->hc->instance->settings.id,
+                                &ac->h_contract_terms,
+                                &refund_coins,
+                                ac);
   if (0 > qs)
   {
-    db->rollback (db->cls);
+    TMH_db->rollback (TMH_db->cls);
     if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
-      begin_transaction (pc);
+      begin_transaction (ac);
       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_FETCH_TRANSACTION_ERROR,
-                           "Merchant database error checking for refunds");
+    resume_abort_with_error (ac,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_ABORT_DB_FETCH_TRANSACTION_ERROR,
+                             "Merchant database error");
     return;
   }
 
-  /* All the coins known to the database have
-   * been processed, now delve into specific case
-   * (pay vs. abort) */
-
-  if (PC_MODE_ABORT_REFUND == pc->mode)
+  qs = TMH_db->commit (TMH_db->cls);
+  if (0 > qs)
   {
-    json_t *terms;
-
-    /* The wallet is going for a refund,
-       (on aborted operation)! */
-
-    /* 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)
+    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 (ac);
       return;
     }
+    resume_abort_with_error (ac,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TALER_EC_ABORT_DB_STORE_ABORT_ERROR,
+                             "Merchant database error: could not commit");
+    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)
+  /* At this point, the refund got correctly committed
+     into the database. Tell exchange about abort/refund. */
+  if (ac->pending > 0)
+  {
+    find_next_exchange (ac);
+    return;
+  }
+  generate_success_response (ac);
+}
+
+
+/**
+ * Try to parse the abort request into the given abort context.
+ * Schedules an error response in the connection on failure.
+ *
+ * @param connection HTTP connection we are receiving abortment on
+ * @param root JSON upload with abortment data
+ * @param ac context we use to handle the abortment
+ * @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_abort (struct MHD_Connection *connection,
+             struct TMH_HandlerContext *hc,
+             struct AbortContext *ac)
+{
+  {
+    json_t *coins;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_json ("coins",
+                             &coins),
+      GNUNET_JSON_spec_fixed_auto ("h_contract",
+                                   &ac->h_contract_terms),
+
+      GNUNET_JSON_spec_end ()
+    };
+    enum GNUNET_GenericReturnValue res;
+
+    res = TALER_MHD_parse_json_data (connection,
+                                     hc->request_body,
+                                     spec);
+    if (GNUNET_YES != res)
     {
-      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");
-      return;
+      GNUNET_break_op (0);
+      return res;
     }
-    qs = db->commit (db->cls);
-    if (0 > qs)
+    ac->coins_cnt = json_array_size (coins);
+    if (0 == ac->coins_cnt)
     {
-      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;
+      GNUNET_JSON_parse_free (spec);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_BAD_REQUEST,
+                                         TALER_EC_ABORT_COINS_ARRAY_EMPTY,
+                                         "coins");
     }
-    /* At this point, the refund got correctly committed
-     * into the database.  */
+    /* note: 1 coin = 1 deposit confirmation expected */
+    ac->pending = ac->coins_cnt;
+    ac->rd = GNUNET_new_array (ac->coins_cnt,
+                               struct RefundDetails);
+    /* This loop populates the array 'rd' in 'ac' */
     {
-      json_t *refunds;
-
-      refunds = json_array ();
-      if (NULL == refunds)
+      unsigned int coins_index;
+      json_t *coin;
+      json_array_foreach (coins, coins_index, coin)
       {
-        GNUNET_break (0);
-        resume_pay_with_error (pc,
-                               MHD_HTTP_INTERNAL_SERVER_ERROR,
-                               TALER_EC_JSON_ALLOCATION_FAILURE,
-                               "could not create JSON array");
-        return;
-      }
-      for (unsigned int i = 0; i<pc->coins_cnt; i++)
-      {
-        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 RefundDetails *rd = &ac->rd[coins_index];
+        const char *exchange_url;
+        struct GNUNET_JSON_Specification ispec[] = {
+          TALER_JSON_spec_amount ("contribution",
+                                  &rd->amount_with_fee),
+          TALER_JSON_spec_amount ("refund_fee",
+                                  &rd->refund_fee),
+          GNUNET_JSON_spec_string ("exchange_url",
+                                   &exchange_url),
+          GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                       &rd->coin_pub),
+          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;
         }
+        rd->exchange_url = GNUNET_strdup (exchange_url);
+        rd->index = coins_index;
+        rd->ac = ac;
       }
-
-      /* 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)));
     }
-    return;
-  } /* End of PC_MODE_ABORT_REFUND */
-
-  /* Default PC_MODE_PAY mode */
+    GNUNET_JSON_parse_free (spec);
+  }
 
-  /* Final termination case: all coins already known, just
-     generate ultimate outcome. */
-  if (0 == pc->pending)
+  /* Check request against contract on file */
   {
-    if (GNUNET_OK != check_payment_sufficient (pc))
+    enum GNUNET_DB_QueryStatus qs;
+    json_t *contract_terms;
+
+    qs = TMH_db->lookup_contract_terms (TMH_db->cls,
+                                        hc->instance->settings.id,
+                                        hc->infix,
+                                        &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_ABORT_DB_FETCH_ABORT_ERROR,
+                                          "Failed to obtain contract terms 
from DB"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
     }
-    /* Payment succeeded, save in database */
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Contract `%s' was fully paid\n",
-                GNUNET_h2s (&pc->h_contract_terms));
-    qs = db->mark_proposal_paid (db->cls,
-                                 &pc->h_contract_terms,
-                                 &pc->mi->pubkey);
-    if (qs < 0)
+    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
     {
-      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_PAYMENTS_ERROR,
-        "Merchant database error: could not mark proposal as 'paid'");
-      return;
+      return (MHD_YES ==
+              TALER_MHD_reply_with_error (connection,
+                                          MHD_HTTP_NOT_FOUND,
+                                          TALER_EC_ABORT_PROPOSAL_NOT_FOUND,
+                                          "Order not found"))
+             ? GNUNET_NO
+             : GNUNET_SYSERR;
     }
 
-    if ( (NULL != pc->session_id) &&
-         (NULL != pc->fulfillment_url) )
+    /* check client provided the right hash and is thus authorized to request 
aborting */
     {
-      qs = db->insert_session_info (db->cls,
-                                    pc->session_id,
-                                    pc->fulfillment_url,
-                                    pc->order_id,
-                                    &pc->mi->pubkey);
+      struct GNUNET_HashCode h_contract_terms;
+
+      if (GNUNET_OK !=
+          TALER_JSON_hash (ac->contract_terms,
+                           &h_contract_terms))
+      {
+        GNUNET_break (0);
+        return (MHD_YES ==
+                TALER_MHD_reply_with_error (connection,
+                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                            
TALER_EC_ABORT_FAILED_COMPUTE_PROPOSAL_HASH,
+                                            "Failed to hash contract terms"))
+               ? GNUNET_NO
+               : GNUNET_SYSERR;
+      }
+      if (0 !=
+          GNUNET_memcmp (&ac->contract_terms,
+                         &h_contract_terms))
+      {
+        GNUNET_break_op (0);
+        return (MHD_YES ==
+                TALER_MHD_reply_with_error (connection,
+                                            MHD_HTTP_BAD_REQUEST,
+                                            
TALER_EC_ABORT_CONTRACT_HASH_MISSMATCH,
+                                            "Provided hash does not match 
order on file"))
+               ? GNUNET_NO
+               : GNUNET_SYSERR;
+      }
     }
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Handling /abort for order `%s' with contract hash `%s'\n",
+                order_id,
+                GNUNET_h2s (&ac->h_contract_terms));
 
-    /* Now commit! */
-    if (0 <= qs)
-      qs = db->commit (db->cls);
-    else
-      db->rollback (db->cls);
-    if (0 > qs)
+    /* Check abort is still possible */
     {
-      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      struct GNUNET_TIME_Absolute refund_deadline;
+      struct GNUNET_TIME_Absolute wire_transfer_deadline;
+      struct GNUNET_TIME_Absolute now;
+      struct GNUNET_JSON_Specification espec[] = {
+        GNUNET_JSON_spec_absolute_time ("refund_deadline",
+                                        &refund_deadline),
+        GNUNET_JSON_spec_absolute_time ("wire_transfer_deadline",
+                                        &wire_transfer_deadline),
+        GNUNET_JSON_spec_end ()
+      };
+
+      /* 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,
+                                       ac->contract_terms,
+                                       espec);
+      if (GNUNET_YES != res)
       {
-        begin_transaction (pc);
-        return;
+        GNUNET_break (0);
+        return res;
+      }
+      now = GNUNET_TIME_absolute_get ();
+      if ( (now.abs_value_us < refund_deadline.abs_value_us) &&
+           ( (0 != refund_deadline.abs_value_us) ||
+             (now.abs_value_us < wire_transfer_deadline.abs_value_us) ) )
+      {
+        /* it is most definitively too late for an abort */
+        return (MHD_YES ==
+                TALER_MHD_reply_with_error (connection,
+                                            MHD_HTTP_GONE,
+                                            TALER_EC_ABORT_TOO_LATE,
+                                            "It is too late to abort the 
payment"))
+               ? GNUNET_NO
+               : GNUNET_SYSERR;
       }
-      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;
     }
-    TMH_long_poll_resume (pc->order_id,
-                          &pc->mi->pubkey,
-                          NULL);
-    generate_success_response (pc);
-    return;
   }
-
-
-  /* we made no DB changes,
-     so we can just rollback */
-  db->rollback (db->cls);
-
-  /* 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 abort 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 AbortContext`
  */
-static MHD_RESULT
-handler_pay_json (struct MHD_Connection *connection,
-                  const json_t *root,
-                  struct PayContext *pc)
+static void
+handle_abort_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 AbortContext *ac = cls;
 
-  /* Payment not finished, suspend while we interact with the exchange */
-  MHD_suspend_connection (connection);
-  pc->suspended = GNUNET_YES;
+  ac->timeout_task = NULL;
+  GNUNET_assert (GNUNET_YES == ac->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 abort with error after timeout\n");
+  if (NULL != ac->fo)
+  {
+    TMH_EXCHANGES_find_exchange_cancel (ac->fo);
+    ac->fo = NULL;
+  }
+  resume_abort_with_error (ac,
+                           MHD_HTTP_REQUEST_TIMEOUT,
+                           TALER_EC_ABORT_EXCHANGE_TIMEOUT,
+                           "likely the exchange did not reply quickly enough");
 }
 
 
 /**
- * Process a payment for a proposal.  Takes data from the given MHD
- * connection.
+ * Abort 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)
+TMH_post_orders_ID_abort (const struct TMH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          struct TMH_HandlerContext *hc)
 {
-  struct PayContext *pc;
-  enum GNUNET_GenericReturnValue res;
-  MHD_RESULT ret;
-  json_t *root;
+  struct AbortContext *ac = hc->ctx;
 
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "In handler for /pay.\n");
   if (NULL == *connection_cls)
   {
-    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;
+    ac = GNUNET_new (struct AbortContext);
+    GNUNET_CONTAINER_DLL_insert (ac_head,
+                                 ac_tail,
+                                 ac);
+    ac->connection = connection;
+    ac->hc = hc;
+    hc->ctx = ac;
+    hc->cc = &abort_context_cleanup;
   }
-  if (GNUNET_SYSERR == pc->suspended)
+  if (GNUNET_SYSERR == ac->suspended)
     return MHD_NO; /* during shutdown, we don't generate any more replies */
-  if (0 != pc->response_code)
+  if (0 != ac->response_code)
   {
+    MHD_RESULT res;
+
     /* We are *done* processing the request,
        just queue the response (!) */
-    if (UINT_MAX == pc->response_code)
+    if (UINT_MAX == ac->response_code)
     {
       GNUNET_break (0);
       return MHD_NO; /* hard error */
     }
     res = MHD_queue_response (connection,
-                              pc->response_code,
-                              pc->response);
-    MHD_destroy_response (pc->response);
-    pc->response = NULL;
+                              ac->response_code,
+                              ac->response);
+    MHD_destroy_response (ac->response);
+    ac->response = NULL;
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Queueing response (%u) for /pay (%s).\n",
-                (unsigned int) pc->response_code,
+                "Queueing response (%u) for /abort (%s).\n",
+                (unsigned int) ac->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_abort (connection,
+                       hc,
+                       ac);
+    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;
+
+  /* Abort not finished, suspend while we interact with the exchange */
+  GNUNET_assert (GNUNET_NO == ac->suspended);
+  MHD_suspend_connection (connection);
+  ac->suspended = GNUNET_YES;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Suspending abort handling while working with the exchange\n");
+  ac->timeout_task = GNUNET_SCHEDULER_add_delayed (ABORT_TIMEOUT,
+                                                   &handle_abort_timeout,
+                                                   ac);
+  begin_transaction (ac);
+  return MHD_YES;
 }
 
 
-/* end of taler-merchant-httpd_pay.c */
+/* end of taler-merchant-httpd_post-orders-ID-abort.c */
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-abort.h 
b/src/backend/taler-merchant-httpd_post-orders-ID-abort.h
index 726a27b..77f55bc 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-abort.h
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-abort.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,41 +14,36 @@
   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-abort.h
+ * @brief headers for POST /orders/$ID/abort 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_ABORT_H
+#define TALER_EXCHANGE_HTTPD_POST_ORDERS_ID_ABORT_H
 #include <microhttpd.h>
 #include "taler-merchant-httpd.h"
 
 
 /**
- * Force all pay contexts to be resumed as we are about
+ * Force all abort contexts to be resumed as we are about
  * to shut down MHD.
  */
 void
-MH_force_pc_resume (void);
+TMH_force_ac_resume (void);
 
 
 /**
- * Manage a payment
+ * Abort 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);
+TMH_post_orders_ID_abort (const struct TMH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          struct TMH_HandlerContext *hc);
 
 #endif
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 8afa41c..f11154e 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -421,6 +421,7 @@ resume_pay_with_response (struct PayContext *pc,
                           unsigned int response_code,
                           struct MHD_Response *response)
 {
+  abort_deposit (pc);
   pc->response_code = response_code;
   pc->response = response;
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -558,9 +559,7 @@ deposit_cb (void *cls,
                 "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);
-
+    /* Transaction failed */
     if (5 == hr->http_status / 100)
     {
       /* internal server error at exchange */
@@ -867,7 +866,6 @@ find_next_exchange (struct PayContext *pc)
 
     if (! dc->found_in_db)
     {
-      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,
@@ -887,7 +885,6 @@ find_next_exchange (struct PayContext *pc)
     }
   }
   pc->current_exchange = NULL;
-  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);
@@ -1776,14 +1773,12 @@ handle_pay_timeout (void *cls)
  * @return MHD result code
  */
 MHD_RESULT
-MH_handler_pay (struct TMH_RequestHandler *rh,
-                struct MHD_Connection *connection,
-                struct TMH_HandlerContext *hc)
+TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
+                        struct MHD_Connection *connection,
+                        struct TMH_HandlerContext *hc)
 {
   struct PayContext *pc = hc->ctx;
 
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "In handler for /pay.\n");
   if (NULL == pc)
   {
     pc = GNUNET_new (struct PayContext);
diff --git a/src/backenddb/plugin_merchantdb_postgres.c 
b/src/backenddb/plugin_merchantdb_postgres.c
index 06f1d7a..2e97c86 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -1654,7 +1654,23 @@ postgres_insert_exchange_signkey (
   struct GNUNET_TIME_Absolute end_date,
   const struct TALER_MasterSignatureP *master_sig)
 {
-  // FIXME!
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (master_pub),
+    GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+    GNUNET_PQ_query_param_absolute_time (&start_date),
+    GNUNET_PQ_query_param_absolute_time (&expire_date),
+    GNUNET_PQ_query_param_absolute_time (&end_date),
+    GNUNET_PQ_query_param_auto_from_type (master_sig),
+    GNUNET_PQ_query_param_end
+  };
+
+  check_connection (pg);
+  postgres_preflight (pg);
+  return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+                                             "insert_exchange_signkey",
+                                             params);
+
 }
 
 
@@ -5282,6 +5298,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
                             "           (NOT paid) ) OR"
                             "         (creation_time + $3 > $4) )",
                             4),
+    /* for postgres_lookup_deposits() */
     GNUNET_PQ_make_prepare ("lookup_deposits",
                             "SELECT"
                             " coin_pub"
@@ -5303,6 +5320,19 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
                             "             FROM merchant_instances"
                             "            WHERE merchant_id=$1))",
                             2),
+    /* for postgres_insert_exchange_signkey() */
+    GNUNET_PQ_make_prepare ("insert_exchange_signkey",
+                            "INSERT INTO merchant_exchange_signing_keys"
+                            "(master_pub"
+                            ",exchange_pub"
+                            ",start_date"
+                            ",expire_date"
+                            ",end_date"
+                            ",master_sig)"
+                            "VALUES"
+                            "($1, $2, $3, $4, $5, $6)",
+                            6),
+    /* for postgres_insert_deposit() */
     GNUNET_PQ_make_prepare ("insert_deposit",
                             "WITH md AS"
                             "  (SELECT account_serial, merchant_serial"

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