gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-merchant] branch master updated (b7c3585 -> bc88e6b)


From: gnunet
Subject: [GNUnet-SVN] [taler-merchant] branch master updated (b7c3585 -> bc88e6b)
Date: Tue, 26 Dec 2017 21:43:24 +0100

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

grothoff pushed a change to branch master
in repository merchant.

    from b7c3585  first refactoring for #5158: allow multiple exchange URLs to 
be in database per /pay operation, plus other minor clean ups
     new 774758e  forgot to remove redundant struct in previous commit
     new bc88e6b  working on #5158, moving exchange_url to coin in /pay API, 
also renaming 'f' field to more clear 'contribution'

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


Summary of changes:
 src/backend/taler-merchant-httpd_pay.c             | 1253 ++++++++++++--------
 src/backend/taler-merchant-httpd_responses.c       |   33 +-
 src/backend/taler-merchant-httpd_responses.h       |   34 +-
 .../taler-merchant-httpd_track-transaction.c       |    6 +-
 src/backend/taler-merchant-httpd_track-transfer.c  |   10 +-
 src/include/taler_merchant_service.h               |  102 +-
 src/lib/merchant_api_pay.c                         |   20 +-
 src/lib/test_merchant_api.c                        |    3 +-
 .../taler-merchant-generate-payments.c             |    2 +-
 9 files changed, 860 insertions(+), 603 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_pay.c 
b/src/backend/taler-merchant-httpd_pay.c
index 1315b9c..8ae6f7d 100644
--- a/src/backend/taler-merchant-httpd_pay.c
+++ b/src/backend/taler-merchant-httpd_pay.c
@@ -40,9 +40,9 @@
 #define PAY_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 
30))
 
 /**
- * How often do we retry the simple INSERT database transaction?
+ * How often do we retry the (complex!) database transaction?
  */
-#define MAX_RETRIES 3
+#define MAX_RETRIES 5
 
 /**
  * Information we keep for an individual call to the /pay handler.
@@ -67,6 +67,11 @@ struct DepositConfirmation
   struct TALER_EXCHANGE_DepositHandle *dh;
 
   /**
+   * URL of the exchange that issued this coin.
+   */
+  char *exchange_url;
+  
+  /**
    * Denomination of this coin.
    */
   struct TALER_DenominationPublicKey denom;
@@ -88,6 +93,11 @@ struct DepositConfirmation
   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;
@@ -185,14 +195,14 @@ struct PayContext
   struct TMH_EXCHANGES_FindOperation *fo;
 
   /**
-   * Placeholder for #TMH_PARSE_post_json() to keep its internal state.
+   * URL of the exchange used for the last @e fo.
    */
-  void *json_parse_context;
-
+  const char *current_exchange;
+  
   /**
-   * Exchange URI given in @e root.
+   * Placeholder for #TMH_PARSE_post_json() to keep its internal state.
    */
-  char *chosen_exchange;
+  void *json_parse_context;
 
   /**
    * Transaction ID given in @e root.
@@ -211,6 +221,16 @@ struct PayContext
   struct GNUNET_HashCode h_wire;
 
   /**
+   * Total wire fees charged by all exchanges involved.  Note: there
+   * is a sublte issue with this value not being correctly calculated
+   * if /pay is called a second time, as then some deposits that are
+   * already in the DB are no longer mapped to an exchange (and thus
+   * no fee is looked up).  Fixing this would be rather complicated,
+   * and is likely simply no worth it.
+   */
+  struct TALER_Amount total_wire_fee;
+  
+  /**
    * 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
@@ -244,6 +264,26 @@ struct PayContext
   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? (Can be given by the frontend
    * or be determined by our configuration via #wire_transfer_delay.)
@@ -281,6 +321,11 @@ struct PayContext
   unsigned int coins_cnt;
 
   /**
+   * How often have we retried the 'main' transaction?
+   */
+  unsigned int retry_counter;
+  
+  /**
    * Number of transactions still pending.  Initially set to
    * @e coins_cnt, decremented on each transaction that
    * successfully finished.
@@ -288,6 +333,14 @@ struct PayContext
   unsigned int pending;
 
   /**
+   * Number of transactions still pending for the currently selected
+   * exchange.  Initially set to the number of coins started at the
+   * exchange, decremented on each transaction that successfully
+   * finished.  Once it hits zero, we pick the next exchange.
+   */
+  unsigned int pending_at_ce;
+
+  /**
    * HTTP status code to use for the reply, i.e 200 for "OK".
    * Special value UINT_MAX is used to indicate hard errors
    * (no reply, return #MHD_NO).
@@ -295,14 +348,6 @@ struct PayContext
   unsigned int response_code;
 
   /**
-   * #GNUNET_NO if the transaction is not in our database,
-   * #GNUNET_YES if the transaction is known to our database,
-   * #GNUNET_SYSERR if the transaction ID is used for a different
-   * transaction in our database.
-   */
-  int transaction_exists;
-
-  /**
    * #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
@@ -409,16 +454,18 @@ sign_success_response (struct PayContext *pc)
   json_t *refunds;
   enum TALER_ErrorCode ec;
   const char *errmsg;
-
-  refunds = TM_get_refund_json (pc->mi, &pc->h_contract_terms, &ec, &errmsg);
-
-  if (NULL == refunds) {
-    return TMH_RESPONSE_make_internal_error (ec, errmsg);
-  }
-
   struct GNUNET_CRYPTO_EddsaSignature sig;
   struct PaymentResponsePS mr;
 
+  refunds = TM_get_refund_json (pc->mi,
+                               &pc->h_contract_terms,
+                               &ec,
+                               &errmsg);
+
+  if (NULL == refunds) 
+    return TMH_RESPONSE_make_error (ec,
+                                   errmsg);
+  
   mr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_PAYMENT_OK);
   mr.purpose.size = htonl (sizeof (mr));
   mr.h_contract_terms = pc->h_contract_terms;
@@ -441,140 +488,23 @@ sign_success_response (struct PayContext *pc)
 
 
 /**
- * Callback to handle a deposit permission's response.
+ * Resume payment processing with an error.
  *
- * @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 http_status HTTP response code, #MHD_HTTP_OK
- *   (200) for successful deposit; 0 if the exchange's reply is bogus (fails
- *   to follow the protocol)
- * @param ec taler-specific error code, #TALER_EC_NONE on success
- * @param sign_key which key did the exchange use to sign the @a proof
- * @param proof the received JSON reply,
- *   should be kept as proof (and, in case of errors, be forwarded to
- *   the customer)
- */
+ * @param pc 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
-deposit_cb (void *cls,
-            unsigned int http_status,
-           enum TALER_ErrorCode ec,
-            const struct TALER_ExchangePublicKeyP *sign_key,
-            const json_t *proof)
+resume_pay_with_error (struct PayContext *pc,
+                      unsigned int http_status,
+                      enum TALER_ErrorCode ec,
+                      const char *msg)
 {
-  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--;
-  if (MHD_HTTP_OK != http_status)
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-               "Deposit operation failed with HTTP code %u\n",
-               http_status);
-    /* Transaction failed; stop all other ongoing deposits */
-    abort_deposit (pc);
-    db->rollback (db->cls);
-
-    if (NULL == proof)
-    {
-      /* We can't do anything meaningful here, the exchange did something 
wrong */
-      resume_pay_with_response (pc,
-                                MHD_HTTP_SERVICE_UNAVAILABLE,
-                                TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:I, 
s:s}",
-                                                             "error", 
"exchange failed",
-                                                            "code", 
(json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
-                                                            "exchange-code", 
(json_int_t) ec,
-                                                            
"exchange-http-status", (json_int_t) http_status,
-                                                             "hint", "The 
exchange provided an unexpected response"));
-    }
-    else
-    {
-      /* Forward error, adding the "coin_pub" for which the
-         error was being generated */
-      json_t *eproof;
-
-      eproof = json_copy ((json_t *) proof);
-      json_object_set_new (eproof,
-                           "coin_pub",
-                           GNUNET_JSON_from_data_auto (&dc->coin_pub));
-      resume_pay_with_response (pc,
-                                http_status,
-                                TMH_RESPONSE_make_json (eproof));
-      json_decref (eproof);
-    }
-    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));
-  for (unsigned int i=0;i<MAX_RETRIES;i++)
-  {
-    qs = db->store_deposit (db->cls,
-                           &pc->h_contract_terms,
-                           &pc->mi->pubkey,
-                           &dc->coin_pub,
-                           pc->chosen_exchange,
-                           &dc->amount_with_fee,
-                           &dc->deposit_fee,
-                           &dc->refund_fee,
-                           sign_key,
-                           proof);
-    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
-      break;
-  }
-  if (0 > qs)
-  {
-    /* Special report if retries insufficient */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-    /* Always report on hard error as well to enable diagnostics */
-    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    /* internal error */
-    abort_deposit (pc);
-    db->rollback (db->cls);
-    /* Forward error including 'proof' for the body */
-    resume_pay_with_response (pc,
-                              MHD_HTTP_INTERNAL_SERVER_ERROR,
-                              TMH_RESPONSE_make_internal_error 
(TALER_EC_PAY_DB_STORE_PAY_ERROR,
-                                                               "Merchant 
database error"));
-    return;
-  }
-
-  if (0 != pc->pending)
-    return; /* still more to do */
-
-  qs = db->mark_proposal_paid (db->cls,
-                               &pc->h_contract_terms,
-                               &pc->mi->pubkey);
-  if (0 > qs)
-  {
-    abort_deposit (pc);
-    db->rollback (db->cls);
-    resume_pay_with_response (pc,
-                              MHD_HTTP_INTERNAL_SERVER_ERROR,
-                              TMH_RESPONSE_make_internal_error 
(TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
-                                                                "Merchant 
database error: could not mark proposal as 'paid'"));
-    return;
-  }
-  qs = db->commit (db->cls);
-  if (0 > qs)
-  {
-    abort_deposit (pc);
-    resume_pay_with_response (pc,
-                              MHD_HTTP_INTERNAL_SERVER_ERROR,
-                              TMH_RESPONSE_make_internal_error 
(TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
-                                                                "Merchant 
database error: could not commit"));
-    return;
-  }
   resume_pay_with_response (pc,
-                            MHD_HTTP_OK,
-                            sign_success_response (pc));
+                           http_status,
+                           TMH_RESPONSE_make_error (ec,
+                                                    msg));
 }
 
 
@@ -613,6 +543,7 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
       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)
@@ -625,11 +556,6 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
     MHD_destroy_response (pc->response);
     pc->response = NULL;
   }
-  if (NULL != pc->chosen_exchange)
-  {
-    GNUNET_free (pc->chosen_exchange);
-    pc->chosen_exchange = NULL;
-  }
   if (NULL != pc->contract_terms)
   {
     json_decref (pc->contract_terms);
@@ -643,99 +569,36 @@ pay_context_cleanup (struct TM_HandlerContext *hc)
 
 
 /**
- * Function called with the result of our exchange lookup.
+ * Check whether the amount paid is sufficient to cover
+ * the contract.
  *
- * @param cls the `struct PayContext`
- * @param mh NULL if exchange was not found to be acceptable
- * @param wire_fee current applicable fee for dealing with @a mh, NULL if not 
available
- * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
+ * @param pc payment context to check
+ * @return taler error code, #TALER_EC_NONE if amount is sufficient
  */
-static void
-process_pay_with_exchange (void *cls,
-                           struct TALER_EXCHANGE_Handle *mh,
-                           const struct TALER_Amount *wire_fee,
-                           int exchange_trusted)
+static enum TALER_ErrorCode
+check_payment_sufficient (struct PayContext *pc)
 {
-  struct PayContext *pc = cls;
   struct TALER_Amount acc_fee;
   struct TALER_Amount acc_amount;
   struct TALER_Amount wire_fee_delta;
   struct TALER_Amount wire_fee_customer_contribution;
-  const struct TALER_EXCHANGE_Keys *keys;
-  enum GNUNET_DB_QueryStatus qs;
-
-  pc->fo = NULL;
-  GNUNET_assert (GNUNET_YES == pc->suspended);
-  if (NULL == mh)
-  {
-    /* The exchange on offer is not in the set of our (trusted)
-       exchanges.  Reject the payment. */
-    GNUNET_break_op (0);
-    resume_pay_with_response (pc,
-                              MHD_HTTP_PRECONDITION_FAILED,
-                              TMH_RESPONSE_make_external_error 
(TALER_EC_PAY_EXCHANGE_REJECTED,
-                                                               "exchange not 
supported"));
-    return;
-  }
-  pc->mh = mh;
-  keys = TALER_EXCHANGE_get_keys (mh);
-  if (NULL == keys)
-  {
-    GNUNET_break (0);
-    resume_pay_with_response (pc,
-                              MHD_HTTP_INTERNAL_SERVER_ERROR,
-                              TMH_RESPONSE_make_internal_error 
(TALER_EC_PAY_EXCHANGE_KEYS_FAILURE,
-                                                               "no keys"));
-    return;
-  }
 
-  /* Total up the fees and the value of the deposited coins! */
   GNUNET_assert (0 != pc->coins_cnt);
   for (unsigned int i=0;i<pc->coins_cnt;i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
-    const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
 
-    denom_details = TALER_EXCHANGE_get_denomination_key (keys,
-                                                        &dc->denom);
-    if (NULL == denom_details)
-    {
-      GNUNET_break_op (0);
-      resume_pay_with_response (pc,
-                                MHD_HTTP_BAD_REQUEST,
-                                TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o, 
s:o}",
-                                                             "error", 
"denomination not found",
-                                                            "code", 
TALER_EC_PAY_DENOMINATION_KEY_NOT_FOUND,
-                                                             "denom_pub", 
GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key),
-                                                             "exchange_keys", 
TALER_EXCHANGE_get_keys_raw (mh)));
-      return;
-    }
-    if (GNUNET_OK !=
-        TMH_AUDITORS_check_dk (mh,
-                               denom_details,
-                               exchange_trusted))
-    {
-      GNUNET_break_op (0);
-      resume_pay_with_response (pc,
-                                MHD_HTTP_BAD_REQUEST,
-                                TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o}",
-                                                             "error", "invalid 
denomination",
-                                                            "code", 
(json_int_t) TALER_EC_PAY_DENOMINATION_KEY_AUDITOR_FAILURE,
-                                                             "denom_pub", 
GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key)));
-      return;
-    }
-    dc->deposit_fee = denom_details->fee_deposit;
-    dc->refund_fee = denom_details->fee_refund;
+    GNUNET_assert (GNUNET_YES == dc->found_in_db);
     if (0 == i)
     {
-      acc_fee = denom_details->fee_deposit;
+      acc_fee = dc->deposit_fee;
       acc_amount = dc->amount_with_fee;
     }
     else
     {
       if ( (GNUNET_OK !=
            TALER_amount_add (&acc_fee,
-                             &denom_details->fee_deposit,
+                             &dc->deposit_fee,
                              &acc_fee)) ||
           (GNUNET_OK !=
            TALER_amount_add (&acc_amount,
@@ -744,11 +607,7 @@ process_pay_with_exchange (void *cls,
       {
        GNUNET_break_op (0);
        /* Overflow in these amounts? Very strange. */
-       resume_pay_with_response (pc,
-                                 MHD_HTTP_BAD_REQUEST,
-                                 TMH_RESPONSE_make_internal_error 
(TALER_EC_PAY_AMOUNT_OVERFLOW,
-                                                                   "Overflow 
adding up amounts"));
-       return;
+       return TALER_EC_PAY_AMOUNT_OVERFLOW;
       }
     }
     if (1 ==
@@ -757,33 +616,22 @@ process_pay_with_exchange (void *cls,
     {
       GNUNET_break_op (0);
       /* fee higher than residual coin value, makes no sense. */
-      resume_pay_with_response (pc,
-                               MHD_HTTP_BAD_REQUEST,
-                                TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o, 
s:o}",
-                                                             "hint", "fee 
higher than coin value",
-                                                            "code", 
(json_int_t) TALER_EC_PAY_FEES_EXCEED_PAYMENT,
-                                                             "f" /* FIXME */, 
TALER_JSON_from_amount (&dc->amount_with_fee),
-                                                             "fee_deposit", 
TALER_JSON_from_amount (&denom_details->fee_deposit)));
-      return;
+      return TALER_EC_PAY_FEES_EXCEED_PAYMENT;
     }
   }
 
   /* Now compare exchange wire fee compared to what we are willing to pay */
   if (GNUNET_YES !=
-      TALER_amount_cmp_currency (wire_fee,
+      TALER_amount_cmp_currency (&pc->total_wire_fee,  
                                  &pc->max_wire_fee))
   {
     GNUNET_break (0);
-    resume_pay_with_response (pc,
-                              MHD_HTTP_INTERNAL_SERVER_ERROR,
-                              TMH_RESPONSE_make_internal_error 
(TALER_EC_PAY_WIRE_FEE_CURRENCY_MISSMATCH,
-                                                                "wire_fee"));
-    return;
+    return TALER_EC_PAY_WIRE_FEE_CURRENCY_MISSMATCH;
   }
 
   if (GNUNET_OK ==
       TALER_amount_subtract (&wire_fee_delta,
-                             wire_fee,
+                             &pc->total_wire_fee, 
                              &pc->max_wire_fee))
   {
     /* Actual wire fee is indeed higher than our maximum, compute
@@ -795,7 +643,7 @@ process_pay_with_exchange (void *cls,
   else
   {
     GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_get_zero (wire_fee->currency,
+                   TALER_amount_get_zero (pc->total_wire_fee.currency,
                                           &wire_fee_customer_contribution));
 
   }
@@ -820,11 +668,7 @@ process_pay_with_exchange (void *cls,
                           &pc->amount))
     {
       GNUNET_break (0);
-      resume_pay_with_response (pc,
-                                MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                TMH_RESPONSE_make_internal_error 
(TALER_EC_PAY_AMOUNT_OVERFLOW,
-                                                                 "overflow"));
-      return;
+      return TALER_EC_PAY_AMOUNT_OVERFLOW;
     }
     /* add wire fee contribution to the total */
     if (GNUNET_OK ==
@@ -837,11 +681,7 @@ process_pay_with_exchange (void *cls,
                                 &total_needed))
     {
       GNUNET_break_op (0);
-      resume_pay_with_response (pc,
-                                MHD_HTTP_METHOD_NOT_ACCEPTABLE,
-                                TMH_RESPONSE_make_external_error 
(TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES,
-                                                                 "insufficient 
funds (including excessive exchange fees to be covered by customer)"));
-      return;
+      return TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES;
     }
   }
   else
@@ -871,11 +711,7 @@ process_pay_with_exchange (void *cls,
                                  &wire_fee_customer_contribution))
       {
         GNUNET_break_op (0);
-        resume_pay_with_response (pc,
-                                  MHD_HTTP_METHOD_NOT_ACCEPTABLE,
-                                  TMH_RESPONSE_make_external_error 
(TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES,
-                                                                    
"insufficient funds (including excessive exchange fees to be covered by 
customer)"));
-        return;
+        return TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES;
       }
     }
 
@@ -884,173 +720,310 @@ process_pay_with_exchange (void *cls,
                                 &pc->amount))
     {
       GNUNET_break_op (0);
-      resume_pay_with_response (pc,
-                                MHD_HTTP_METHOD_NOT_ACCEPTABLE,
-                                TMH_RESPONSE_make_external_error 
(TALER_EC_PAY_PAYMENT_INSUFFICIENT,
-                                                                 "insufficient 
funds"));
-      return;
+      return TALER_EC_PAY_PAYMENT_INSUFFICIENT;
     }
   }
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Exchange and fee structure OK. Initiating deposit operation for 
coins\n");
+  return TALER_EC_NONE;
+}
 
 
-  if (GNUNET_OK != db->start (db->cls))
+/**
+ * Generate full error response based on the @a ec
+ *
+ * @param pc context for which to generate the error
+ * @param ec error code identifying the issue
+ */
+static void
+generate_error_response (struct PayContext *pc,
+                        enum TALER_ErrorCode ec)
+{
+  switch (ec)
   {
+  case TALER_EC_PAY_AMOUNT_OVERFLOW:
+    resume_pay_with_error (pc,
+                          MHD_HTTP_BAD_REQUEST,
+                          ec,
+                          "Overflow adding up amounts");
+    break;
+  case TALER_EC_PAY_FEES_EXCEED_PAYMENT:
+    resume_pay_with_error (pc,
+                          MHD_HTTP_BAD_REQUEST,
+                          ec,
+                          "Deposit fees exceed coin's contribution");
+    break;
+  case TALER_EC_PAY_PAYMENT_INSUFFICIENT_DUE_TO_FEES:
+    resume_pay_with_error (pc,
+                          MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+                          ec,
+                          "insufficient funds (including excessive exchange 
fees to be covered by customer)");
+    break;
+  case TALER_EC_PAY_PAYMENT_INSUFFICIENT:
+    resume_pay_with_error (pc,
+                          MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+                          ec,
+                          "insufficient funds");
+    break;
+  case TALER_EC_PAY_WIRE_FEE_CURRENCY_MISSMATCH:
+    resume_pay_with_error (pc,
+                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+                          ec,
+                          "wire_fee currency does not match");
+    break;
+  default:
+    resume_pay_with_error (pc,
+                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+                          ec,
+                          "unexpected error code");
     GNUNET_break (0);
-    resume_pay_with_response (pc,
-                              MHD_HTTP_INTERNAL_SERVER_ERROR,
-                              TMH_RESPONSE_make_json_pack ("{s:s, s:I}",
-                                                           "hint", "Merchant 
database error: could not start transaction",
-                                                           "code", 
(json_int_t) TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR));
-    return;
+    break;
   }
+}
 
-  /* Check if transaction is already known, if not store it. */
-  {
-    struct GNUNET_HashCode h_xwire;
-    struct GNUNET_TIME_Absolute xtimestamp;
-    struct GNUNET_TIME_Absolute xrefund;
-    struct TALER_Amount xtotal_amount;
-    
-    qs = db->find_transaction (db->cls,
-                              &pc->h_contract_terms,
-                              &pc->mi->pubkey,
-                              &h_xwire,
-                              &xtimestamp,
-                              &xrefund,
-                              &xtotal_amount);
-    if (0 > qs)
-    {
-      /* single, read-only SQL statements should never cause
-        serialization problems */
-      GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
-      /* Always report on hard error as well to enable diagnostics */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      /* FIXME: factor common logic of these calls into a function! */
-      resume_pay_with_response (pc,
-                               MHD_HTTP_INTERNAL_SERVER_ERROR,
-                               TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
-                                                            "code", 
(json_int_t) TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
-                                                            "hint", "Merchant 
database error"));
-      return;
-    }
-    if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == pc->transaction_exists) &&
-        ( (0 != memcmp (&h_xwire,
-                        &pc->mi->h_wire,
-                        sizeof (struct GNUNET_HashCode))) ||
-          (xtimestamp.abs_value_us != pc->timestamp.abs_value_us) ||
-          (xrefund.abs_value_us != pc->refund_deadline.abs_value_us) ||
-          (0 != TALER_amount_cmp (&xtotal_amount,
-                                  &pc->amount) ) ) )
-    {
-      GNUNET_break (0);
-      /* FIXME: factor common logic of these calls into a function! */
-      resume_pay_with_response (pc,
-                               MHD_HTTP_BAD_REQUEST,
-                               TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
-                                                            "code", 
(json_int_t) TALER_EC_PAY_DB_TRANSACTION_ID_CONFLICT,
-                                                            "hint", 
"Transaction ID reused with different transaction details"));
-      return;
-    }
-  }
-  
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == pc->transaction_exists)
-  {
-    struct GNUNET_TIME_Absolute now;
-    enum GNUNET_DB_QueryStatus qs_st;
 
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Dealing with new transaction `%s'\n",
-                GNUNET_h2s (&pc->h_contract_terms));
+/**
+ * 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);
 
-    now = GNUNET_TIME_absolute_get ();
-    if (now.abs_value_us > pc->pay_deadline.abs_value_us)
-    {
-      /* Time expired, we don't accept this payment now! */
-      const char *pd_str;
 
-      pd_str = GNUNET_STRINGS_absolute_time_to_string (pc->pay_deadline);
-      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
-                  "Attempt to pay coins for expired contract. Deadline: 
`%s'\n",
-                 pd_str);
-      resume_pay_with_response (pc,
-                                MHD_HTTP_BAD_REQUEST,
-                                TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
-                                                             "code", 
(json_int_t) TALER_EC_PAY_OFFER_EXPIRED,
-                                                             "hint", "The time 
to pay for this contract has expired."));
-      return;
-    }
+/**
+ * Begin of the DB transaction.  If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
+ *
+ * @param pc payment context to transact
+ */
+static void
+begin_transaction (struct PayContext *pc);
 
-    qs_st = db->store_transaction (db->cls,
-                                   &pc->h_contract_terms,
-                                   &pc->mi->pubkey,
-                                   &pc->mi->h_wire,
-                                   pc->timestamp,
-                                   pc->refund_deadline,
-                                   &pc->amount);
-    /* Only retry if SOFT error occurred.  Exit in case of OK or HARD failure 
*/
-    if (GNUNET_DB_STATUS_SOFT_ERROR == qs_st)
+
+/**
+ * Callback to handle a deposit permission's response.
+ *
+ * @param cls a `struct DepositConfirmation` (i.e. a pointer
+ *   into the global array of confirmations and an index for this call
+ *   in that array). That way, the last executed callback can detect
+ *   that no other confirmations are on the way, and can pack a response
+ *   for the wallet
+ * @param http_status HTTP response code, #MHD_HTTP_OK
+ *   (200) for successful deposit; 0 if the exchange's reply is bogus (fails
+ *   to follow the protocol)
+ * @param ec taler-specific error code, #TALER_EC_NONE on success
+ * @param sign_key which key did the exchange use to sign the @a proof
+ * @param proof the received JSON reply,
+ *   should be kept as proof (and, in case of errors, be forwarded to
+ *   the customer)
+ */
+static void
+deposit_cb (void *cls,
+            unsigned int http_status,
+           enum TALER_ErrorCode ec,
+            const struct TALER_ExchangePublicKeyP *sign_key,
+            const json_t *proof)
+{
+  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 != http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+               "Deposit operation failed with HTTP code %u\n",
+               http_status);
+    /* Transaction failed; stop all other ongoing deposits */
+    abort_deposit (pc);
+    db->rollback (db->cls);
+
+    if (NULL == proof)
     {
-      db->rollback (db->cls);
-      GNUNET_break (0);  // FIXME: implement proper retries!
+      /* We can't do anything meaningful here, the exchange did something 
wrong */
       resume_pay_with_response (pc,
-                                MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
-                                                             "code", 
(json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
-                                                             "hint", "Soft 
merchant database error: retries not implemented"));
-      return;
+                                MHD_HTTP_SERVICE_UNAVAILABLE,
+                                TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:I, 
s:s}",
+                                                             "error", 
"exchange failed",
+                                                            "code", 
(json_int_t) TALER_EC_PAY_EXCHANGE_FAILED,
+                                                            "exchange-code", 
(json_int_t) ec,
+                                                            
"exchange-http-status", (json_int_t) http_status,
+                                                             "hint", "The 
exchange provided an unexpected response"));
     }
-    /* Exit in case of HARD failure */
-    if (GNUNET_DB_STATUS_HARD_ERROR == qs_st)
+    else
     {
-      GNUNET_break (0);
-      db->rollback (db->cls);
+      /* Forward error, adding the "coin_pub" for which the
+         error was being generated */
+      json_t *eproof;
+
+      eproof = json_copy ((json_t *) proof);
+      json_object_set_new (eproof,
+                           "coin_pub",
+                           GNUNET_JSON_from_data_auto (&dc->coin_pub));
       resume_pay_with_response (pc,
-                                MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
-                                                             "code", 
(json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
-                                                             "hint", "Merchant 
database error: hard error while storing transaction"));
+                                http_status,
+                                TMH_RESPONSE_make_json (eproof));
+      json_decref (eproof);
     }
-
-    /**
-     * Break if we couldn't modify one, and only one line; this
-     * includes hard errors.
-     */
-    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs_st)
+    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! */
+  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,
+                         sign_key,
+                         proof);
+  if (0 > qs)
+  {
+    /* Special report if retries insufficient */
+    abort_deposit (pc);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     {
-      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
-                  "Unexpected query status %d while storing /pay 
transaction!\n",
-                  (int) qs_st);
-      resume_pay_with_response (pc,
-                                MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
-                                                             "code", 
(json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
-                                                             "hint", "Merchant 
database error: failed to store transaction"));
+      begin_transaction (pc);
       return;
     }
-  } /* end of if (GNUNET_NO == pc->transaction_exists) */
+    /* 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);
+}
+
+
+/**
+ * Function called with the result of our exchange lookup.
+ *
+ * @param cls the `struct PayContext`
+ * @param mh NULL if exchange was not found to be acceptable
+ * @param wire_fee current applicable fee for dealing with @a mh, NULL if not 
available
+ * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config
+ */
+static void
+process_pay_with_exchange (void *cls,
+                           struct TALER_EXCHANGE_Handle *mh,
+                           const struct TALER_Amount *wire_fee,
+                           int exchange_trusted)
+{
+  struct PayContext *pc = cls;
+  const struct TALER_EXCHANGE_Keys *keys;
+
+  pc->fo = NULL;
+  GNUNET_assert (GNUNET_YES == pc->suspended);
+  if (NULL == mh)
+  {
+    /* The exchange on offer is not in the set of our (trusted)
+       exchanges.  Reject the payment. */
+    GNUNET_break_op (0);
+    resume_pay_with_error (pc,
+                          MHD_HTTP_PRECONDITION_FAILED,
+                          TALER_EC_PAY_EXCHANGE_REJECTED,
+                          "exchange not supported");
+    return;
+  }
+  if (GNUNET_OK !=
+      TALER_amount_add (&pc->total_wire_fee,
+                       &pc->total_wire_fee,
+                       wire_fee))
+  {
+    GNUNET_break_op (0);
+    resume_pay_with_error (pc,
+                          MHD_HTTP_PRECONDITION_FAILED,
+                          TALER_EC_PAY_EXCHANGE_REJECTED,
+                          "exchange charges incompatible wire fee");
+    return;
+  }
+  pc->mh = mh;
+  keys = TALER_EXCHANGE_get_keys (mh);
+  if (NULL == keys)
+  {
+    GNUNET_break (0);
+    resume_pay_with_error (pc,
+                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+                          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 */
+  /* Initiate /deposit 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++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
+    const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
 
     if (GNUNET_YES == dc->found_in_db)
       continue;
+    if (0 != strcmp (dc->exchange_url,
+                    pc->current_exchange))
+      continue;
+    denom_details = TALER_EXCHANGE_get_denomination_key (keys,
+                                                        &dc->denom);
+    if (NULL == denom_details)
+    {
+      GNUNET_break_op (0);
+      resume_pay_with_response (pc,
+                                MHD_HTTP_BAD_REQUEST,
+                                TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o, 
s:o}",
+                                                             "error", 
"denomination not found",
+                                                            "code", 
TALER_EC_PAY_DENOMINATION_KEY_NOT_FOUND,
+                                                             "denom_pub", 
GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_key),
+                                                             "exchange_keys", 
TALER_EXCHANGE_get_keys_raw (mh)));
+      return;
+    }
+    if (GNUNET_OK !=
+        TMH_AUDITORS_check_dk (mh,
+                               denom_details,
+                               exchange_trusted))
+    {
+      GNUNET_break_op (0);
+      resume_pay_with_response (pc,
+                                MHD_HTTP_BAD_REQUEST,
+                                TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:o}",
+                                                             "error", "invalid 
denomination",
+                                                            "code", 
(json_int_t) TALER_EC_PAY_DENOMINATION_KEY_AUDITOR_FAILURE,
+                                                             "denom_pub", 
GNUNET_JSON_from_rsa_public_key (dc->denom.rsa_public_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->mi->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);
-
     dc->dh = TALER_EXCHANGE_deposit (mh,
                                      &dc->amount_with_fee,
                                      pc->wire_transfer_deadline,
@@ -1070,7 +1043,6 @@ process_pay_with_exchange (void *cls,
       /* Signature was invalid.  If the exchange was unavailable,
        * we'd get that information in the callback. */
       GNUNET_break_op (0);
-      db->rollback (db->cls);
       resume_pay_with_response (pc,
                                 MHD_HTTP_UNAUTHORIZED,
                                 TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:i}",
@@ -1080,7 +1052,47 @@ process_pay_with_exchange (void *cls,
                                                              "coin_idx", i));
       return;
     }
+    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
+ */
+static void
+find_next_exchange (struct PayContext *pc)
+{
+  for (unsigned int i=0;i<pc->coins_cnt;i++)
+  {
+    struct DepositConfirmation *dc = &pc->dc[i];
+
+    if (GNUNET_YES != dc->found_in_db)
+    {
+      pc->current_exchange = dc->exchange_url;
+      pc->fo = TMH_EXCHANGES_find_exchange (pc->current_exchange,
+                                           pc->mi->wire_method,
+                                           &process_pay_with_exchange,
+                                           pc);
+      if (NULL == pc->fo)
+      {
+       GNUNET_break (0);
+       resume_pay_with_error (pc,
+                              MHD_HTTP_INTERNAL_SERVER_ERROR,
+                              TALER_EC_PAY_EXCHANGE_FAILED,
+                              "Failed to lookup exchange by URL");
+       return;
+      }
+      return;
+    }
   }
+  pc->current_exchange = NULL;
+  /* We are done with all the HTTP requests, go back and try
+     the 'big' database transaction! (It should work now!) */
+  begin_transaction (pc);
 }
 
 
@@ -1103,10 +1115,10 @@ handle_pay_timeout (void *cls)
     TMH_EXCHANGES_find_exchange_cancel (pc->fo);
     pc->fo = NULL;
   }
-  resume_pay_with_response (pc,
-                            MHD_HTTP_SERVICE_UNAVAILABLE,
-                            TMH_RESPONSE_make_internal_error 
(TALER_EC_PAY_EXCHANGE_TIMEOUT,
-                                                             "exchange not 
reachable"));
+  resume_pay_with_error (pc,
+                        MHD_HTTP_SERVICE_UNAVAILABLE,
+                        TALER_EC_PAY_EXCHANGE_TIMEOUT,
+                        "exchange not reachable");
 }
 
 
@@ -1130,6 +1142,7 @@ check_coin_paid (void *cls,
                  const struct TALER_Amount *amount_with_fee,
                  const struct TALER_Amount *deposit_fee,
                  const struct TALER_Amount *refund_fee,
+                // FIXME: also store AND fetch wire fee!
                  const json_t *exchange_proof)
 {
   struct PayContext *pc = cls;
@@ -1144,6 +1157,9 @@ check_coin_paid (void *cls,
   for (unsigned int i=0;i<pc->coins_cnt;i++)
   {
     struct DepositConfirmation *dc = &pc->dc[i];
+    
+    if (GNUNET_YES == dc->found_in_db)
+      continue; /* processed earlier */
     /* Get matching coin from results*/
     if ( (0 != memcmp (coin_pub,
                        &dc->coin_pub,
@@ -1151,16 +1167,35 @@ check_coin_paid (void *cls,
          (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)));
-
+                TALER_b2s (coin_pub,
+                          sizeof (*coin_pub)));
+    if (GNUNET_OK !=
+       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 (GNUNET_OK !=
+       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; // TBD...
+    dc->amount_with_fee = *amount_with_fee;
     dc->found_in_db = GNUNET_YES;
-    /**
-     * What happens if a (mad) wallet sends new coins on a
-     * contract that it already paid for?
-     */
     pc->pending--;
   }
 }
@@ -1187,15 +1222,16 @@ parse_pay (struct MHD_Connection *connection,
   json_t *coin;
   json_t *merchant;
   unsigned int coins_index;
-  const char *chosen_exchange;
   const char *order_id;
   struct TALER_MerchantPublicKeyP merchant_pub;
   int res;
   struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_json ("coins", &coins),
-    GNUNET_JSON_spec_string ("exchange", &chosen_exchange),
-    GNUNET_JSON_spec_string ("order_id", &order_id),
-    GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub),
+    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;
@@ -1295,7 +1331,6 @@ parse_pay (struct MHD_Connection *connection,
               GNUNET_STRINGS_data_to_string_alloc (&pc->mi->pubkey,
                                                    sizeof (pc->mi->pubkey)));
 
-  pc->chosen_exchange = GNUNET_strdup (chosen_exchange);
   {
     struct GNUNET_JSON_Specification espec[] = {
       GNUNET_JSON_spec_absolute_time ("refund_deadline",
@@ -1379,6 +1414,10 @@ parse_pay (struct MHD_Connection *connection,
                    TALER_amount_get_zero (pc->max_fee.currency,
                                           &pc->max_wire_fee));
   }
+  /* Initialize wire fee total */
+  GNUNET_assert (GNUNET_OK ==
+                TALER_amount_get_zero (pc->max_fee.currency,
+                                       &pc->total_wire_fee));
   if (NULL != json_object_get (pc->contract_terms,
                                "wire_fee_amortization"))
   {
@@ -1420,12 +1459,20 @@ parse_pay (struct MHD_Connection *connection,
   json_array_foreach (coins, coins_index, coin)
   {
     struct DepositConfirmation *dc = &pc->dc[coins_index];
+    const char *exchange_url;
     struct GNUNET_JSON_Specification spec[] = {
-      TALER_JSON_spec_denomination_public_key ("denom_pub", &dc->denom),
-      TALER_JSON_spec_amount ("f" /* FIXME */, &dc->amount_with_fee),
-      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),
+      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()
     };
 
@@ -1436,9 +1483,9 @@ parse_pay (struct MHD_Connection *connection,
     {
       GNUNET_JSON_parse_free (spec);
       GNUNET_break_op (0);
-      return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+      return res;
     }
-
+    dc->exchange_url = GNUNET_strdup (exchange_url);
     dc->index = coins_index;
     dc->pc = pc;
   }
@@ -1449,29 +1496,80 @@ parse_pay (struct MHD_Connection *connection,
 
 
 /**
- * Process a payment for a proposal.
+ * Function called with information about a refund.
+ * Check if this coin was claimed by the wallet for the
+ * transaction, and if so add the refunded amount to the
+ * pc's "total_refunded" amount.
  *
- * @param connection HTTP connection we are receiving payment on
- * @param root JSON upload with payment data
- * @param pc context we use to handle the payment
- * @return value to return to MHD (#MHD_NO to drop connection,
- *         #MHD_YES to keep handling it)
+ * @param cls closure with a `struct PayContext`
+ * @param coin_pub public coin from which the refund comes from
+ * @param rtransaction_id identificator of the refund
+ * @param reason human-readable explaination of the refund
+ * @param refund_amount refund amount which is being taken from coin_pub
+ * @param refund_fee cost of this refund operation
  */
-static int
-handler_pay_json (struct MHD_Connection *connection,
-                  const json_t *root,
-                  struct PayContext *pc)
+static void
+check_coin_refunded (void *cls,
+                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    uint64_t rtransaction_id,
+                    const char *reason,
+                    const struct TALER_Amount *refund_amount,
+                    const struct TALER_Amount *refund_fee)
 {
-  int ret;
+  struct PayContext *pc = cls;
+
+  /* FIXME: to be implemented (#5158) */
+  (void) pc;
+}
+
+
+/**
+ * Begin of the DB transaction.  If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
+ *
+ * @param pc payment context to transact
+ */
+static void
+begin_transaction (struct PayContext *pc)
+{  
   enum GNUNET_DB_QueryStatus qs;
 
-  ret = parse_pay (connection,
-                   root,
-                   pc);
-  if (GNUNET_OK != ret)
-    return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+  /* Avoid re-trying transactions on soft errors forever! */
+  if (pc->retry_counter++ > MAX_RETRIES)
+  {
+    GNUNET_break (0);
+    resume_pay_with_response (pc,
+                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                             TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+                                                          "code", (json_int_t) 
TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
+                                                          "hint", "Soft 
merchant database error: retry counter exceeded"));
+    return;
+  }
+  
+  GNUNET_assert (GNUNET_YES == pc->suspended);
 
-  /* Check if this payment attempt has already succeeded */
+  /* First, try to see if we have all we need already done */
+  if (GNUNET_OK != db->start (db->cls))
+  {
+    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 start 
transaction)");
+    return;
+  }
+  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));
+                        
+  /* Check if some of these coins already succeeded */
   qs = db->find_payments (db->cls,
                          &pc->h_contract_terms,
                          &pc->mi->pubkey,
@@ -1479,50 +1577,269 @@ handler_pay_json (struct MHD_Connection *connection,
                          pc);
   if (0 > qs)
   {
-    /* single, read-only SQL statements should never cause
-       serialization problems */
-    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+    db->rollback (db->cls);
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+    {
+      begin_transaction (pc);
+      return;
+    }
     /* Always report on hard error as well to enable diagnostics */
     GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    return TMH_RESPONSE_reply_internal_error (connection,
-                                             
TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
-                                             "Merchant database error");
+    resume_pay_with_error (pc,
+                          MHD_HTTP_INTERNAL_SERVER_ERROR,
+                          TALER_EC_PAY_DB_FETCH_TRANSACTION_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);
+  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_FETCH_TRANSACTION_ERROR,
+                          "Merchant database error");
+    return;
   }
+
+  /* FIXME: check if wallet is going for a refund,
+     (on aborted operation), or for a payment! #5158 */
+
+
+  /* Final termination case: all coins already known, just 
+     generate ultimate outcome. */
   if (0 == pc->pending)
   {
-    struct MHD_Response *resp;
-    int ret;
+    enum TALER_ErrorCode ec;
+
+    ec = check_payment_sufficient (pc);
+    if (TALER_EC_NONE == ec)
+    {
+      /* Payment succeeded, commit! */
+      qs = db->mark_proposal_paid (db->cls,
+                                  &pc->h_contract_terms,
+                                  &pc->mi->pubkey);
+      if (0 <= qs)
+       qs = db->commit (db->cls);
+      if (0 > qs)
+      {    
+       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;
+      }
+      resume_pay_with_response (pc,
+                               MHD_HTTP_OK,
+                               sign_success_response (pc));
+      return;
+    }
+    generate_error_response (pc,
+                            ec);
+    return;
+  }
+
+  
+  /* Check if transaction is already known, if not store it. */
+  {
+    struct GNUNET_HashCode h_xwire;
+    struct GNUNET_TIME_Absolute xtimestamp;
+    struct GNUNET_TIME_Absolute xrefund;
+    struct TALER_Amount xtotal_amount;
+    
+    qs = db->find_transaction (db->cls,
+                              &pc->h_contract_terms,
+                              &pc->mi->pubkey,
+                              &h_xwire,
+                              &xtimestamp,
+                              &xrefund,
+                              &xtotal_amount);
+    if (0 > qs)
+    {
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      {
+       db->rollback (db->cls);
+       begin_transaction (pc);
+       return;
+      }
+      /* Always report on hard error as well to enable diagnostics */
+      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+      db->rollback (db->cls);
+      resume_pay_with_response (pc,
+                               MHD_HTTP_INTERNAL_SERVER_ERROR,
+                               TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+                                                            "code", 
(json_int_t) TALER_EC_PAY_DB_FETCH_TRANSACTION_ERROR,
+                                                            "hint", "Merchant 
database error"));
+      return;
+    }
+    if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
+        ( (0 != memcmp (&h_xwire,
+                        &pc->mi->h_wire,
+                        sizeof (struct GNUNET_HashCode))) ||
+          (xtimestamp.abs_value_us != pc->timestamp.abs_value_us) ||
+          (xrefund.abs_value_us != pc->refund_deadline.abs_value_us) ||
+          (0 != TALER_amount_cmp (&xtotal_amount,
+                                  &pc->amount) ) ) )
+    {
+      GNUNET_break (0);
+      db->rollback (db->cls);
+      resume_pay_with_response (pc,
+                               MHD_HTTP_BAD_REQUEST,
+                               TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+                                                            "code", 
(json_int_t) TALER_EC_PAY_DB_TRANSACTION_ID_CONFLICT,
+                                                            "hint", 
"Transaction ID reused with different transaction details"));
+      return;
+    }
+  }
+  
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    struct GNUNET_TIME_Absolute now;
+    enum GNUNET_DB_QueryStatus qs_st;
 
-    /* Payment succeeded in the past; take short cut
-       and accept immediately */
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Payment succeeded in the past; taking short cut");
-    resp = sign_success_response (pc);
-    ret = MHD_queue_response (connection,
-                             MHD_HTTP_OK,
-                             resp);
-    MHD_destroy_response (resp);
-    return ret;
+                "Dealing with new transaction `%s'\n",
+                GNUNET_h2s (&pc->h_contract_terms));
+
+    now = GNUNET_TIME_absolute_get ();
+    if (now.abs_value_us > pc->pay_deadline.abs_value_us)
+    {
+      /* Time expired, we don't accept this payment now! */
+      const char *pd_str;
+
+      pd_str = GNUNET_STRINGS_absolute_time_to_string (pc->pay_deadline);
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Attempt to pay coins for expired contract. Deadline: 
`%s'\n",
+                 pd_str);
+      db->rollback (db->cls);
+      resume_pay_with_response (pc,
+                                MHD_HTTP_BAD_REQUEST,
+                                TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+                                                             "code", 
(json_int_t) TALER_EC_PAY_OFFER_EXPIRED,
+                                                             "hint", "The time 
to pay for this contract has expired."));
+      return;
+    }
+
+    qs_st = db->store_transaction (db->cls,
+                                   &pc->h_contract_terms,
+                                   &pc->mi->pubkey,
+                                   &pc->mi->h_wire,
+                                   pc->timestamp,
+                                   pc->refund_deadline,
+                                   &pc->amount);
+    /* Only retry if SOFT error occurred.  Exit in case of OK or HARD failure 
*/
+    if (GNUNET_DB_STATUS_SOFT_ERROR == qs_st)
+    {
+      db->rollback (db->cls);
+      begin_transaction (pc);
+      return;
+    }
+    /* Exit in case of HARD failure */
+    if (GNUNET_DB_STATUS_HARD_ERROR == qs_st)
+    {
+      GNUNET_break (0);
+      db->rollback (db->cls);
+      resume_pay_with_response (pc,
+                                MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+                                                             "code", 
(json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
+                                                             "hint", "Merchant 
database error: hard error while storing transaction"));
+    }
+
+    /**
+     * Break if we couldn't modify one, and only one line; this
+     * includes hard errors.
+     */
+    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs_st)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Unexpected query status %d while storing /pay 
transaction!\n",
+                  (int) qs_st);
+      db->rollback (db->cls);
+      resume_pay_with_response (pc,
+                                MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
+                                                             "code", 
(json_int_t) TALER_EC_PAY_DB_STORE_TRANSACTION_ERROR,
+                                                             "hint", "Merchant 
database error: failed to store transaction"));
+      return;
+    }
+
+    qs = db->commit (db->cls);
+    if (0 > qs)
+    {
+      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+      {
+       db->rollback (db->cls);
+       begin_transaction (pc);
+       return;
+      }
+      resume_pay_with_error (pc,
+                            MHD_HTTP_INTERNAL_SERVER_ERROR,
+                            TALER_EC_PAY_DB_STORE_PAYMENTS_ERROR,
+                            "Merchant database error: could not commit");
+      return;
+    }
+  } /* end of if (GNUNET_NO == qs) */
+  else
+  {
+    /* transaction record already existed, 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);
+}
 
-  MHD_suspend_connection (connection);
-  pc->suspended = GNUNET_YES;
 
-  /* Find the responsible exchange, this may take a while... */
-  pc->fo = TMH_EXCHANGES_find_exchange (pc->chosen_exchange,
-                                        pc->mi->wire_method,
-                                        &process_pay_with_exchange,
-                                        pc);
+/**
+ * Process a payment for a proposal.
+ *
+ * @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)
+ */
+static int
+handler_pay_json (struct MHD_Connection *connection,
+                  const json_t *root,
+                  struct PayContext *pc)
+{
+  int ret;
 
-  /* ... so we suspend connection until the last coin has been ack'd
-     or until we have encountered a hard error.  Eventually, we will
-     resume the connection and send back a response using
-     #resume_pay_with_response(). */
+  ret = parse_pay (connection,
+                   root,
+                   pc);
+  if (GNUNET_OK != ret)
+    return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+  MHD_suspend_connection (connection);
+  pc->suspended = GNUNET_YES;
   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;
 }
 
@@ -1587,13 +1904,7 @@ MH_handler_pay (struct TMH_RequestHandler *rh,
                res ? "OK" : "FAILED");
     return res;
   }
-  if (NULL != pc->chosen_exchange)
-  {
-    // FIXME: explain in comment why this could happen!
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-               "Shouldn't be here. Old MHD version?\n");
-    return MHD_YES;
-  }
+
   res = TMH_PARSE_post_json (connection,
                             &pc->json_parse_context,
                             upload_data,
diff --git a/src/backend/taler-merchant-httpd_responses.c 
b/src/backend/taler-merchant-httpd_responses.c
index 1417fa2..71d0406 100644
--- a/src/backend/taler-merchant-httpd_responses.c
+++ b/src/backend/taler-merchant-httpd_responses.c
@@ -181,11 +181,10 @@ TMH_RESPONSE_reply_json_pack (struct MHD_Connection 
*connection,
  * @return a MHD response object
  */
 struct MHD_Response *
-TMH_RESPONSE_make_internal_error (enum TALER_ErrorCode ec,
-                                 const char *hint)
+TMH_RESPONSE_make_error (enum TALER_ErrorCode ec,
+                        const char *hint)
 {
-  return TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:s}",
-                                      "error", "internal error",
+  return TMH_RESPONSE_make_json_pack ("{s:I, s:s}",
                                      "code", (json_int_t) ec,
                                       "hint", hint);
 }
@@ -206,8 +205,7 @@ TMH_RESPONSE_reply_internal_error (struct MHD_Connection 
*connection,
 {
   return TMH_RESPONSE_reply_json_pack (connection,
                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       "{s:s, s:I, s:s}",
-                                       "error", "internal error",
+                                       "{s:I, s:s}",
                                       "code", (json_int_t) ec,
                                        "hint", hint);
 }
@@ -354,32 +352,13 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection 
*connection,
 {
   return TMH_RESPONSE_reply_json_pack (connection,
                                        MHD_HTTP_BAD_REQUEST,
-                                       "{s:s, s:I, s:s}",
-                                       "error", "client error",
+                                       "{s:I, s:s}",
                                       "code", (json_int_t) ec,
                                        "hint", hint);
 }
 
 
 /**
- * Create a response indicating an external error.
- *
- * @param ec error code to return
- * @param hint hint about the internal error's nature
- * @return a MHD response object
- */
-struct MHD_Response *
-TMH_RESPONSE_make_external_error (enum TALER_ErrorCode ec,
-                                 const char *hint)
-{
-  return TMH_RESPONSE_make_json_pack ("{s:s, s:I, s:s}",
-                                      "error", "client error",
-                                     "code", (json_int_t) ec,
-                                      "hint", hint);
-}
-
-
-/**
  * Send a response indicating a missing argument.
  *
  * @param connection the MHD connection to use
@@ -394,7 +373,7 @@ TMH_RESPONSE_reply_arg_missing (struct MHD_Connection 
*connection,
 {
   return TMH_RESPONSE_reply_json_pack (connection,
                                        MHD_HTTP_BAD_REQUEST,
-                                       "{ s:s, s:I, s:s}",
+                                       "{s:s, s:I, s:s}",
                                        "error", "missing parameter",
                                       "code", (json_int_t) ec,
                                        "parameter", param_name);
diff --git a/src/backend/taler-merchant-httpd_responses.h 
b/src/backend/taler-merchant-httpd_responses.h
index a96b696..3dbd004 100644
--- a/src/backend/taler-merchant-httpd_responses.h
+++ b/src/backend/taler-merchant-httpd_responses.h
@@ -143,20 +143,6 @@ TMH_RESPONSE_reply_bad_request (struct MHD_Connection 
*connection,
 
 
 /**
- * Send a response indicating an internal error.
- *
- * @param connection the MHD connection to use
- * @param ec error code to return
- * @param hint hint about the internal error's nature
- * @return a MHD result code
- */
-int
-TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection,
-                                  enum TALER_ErrorCode ec,
-                                   const char *hint);
-
-
-/**
  * Create a response indicating an internal error.
  *
  * @param ec error code to return
@@ -183,15 +169,29 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection 
*connection,
 
 
 /**
- * Create a response indicating an external error.
+ * Send a response indicating an internal error.
+ *
+ * @param connection the MHD connection to use
+ * @param ec error code to return
+ * @param hint hint about the internal error's nature
+ * @return a MHD result code
+ */
+int
+TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection,
+                                  enum TALER_ErrorCode ec,
+                                   const char *hint);
+
+
+/**
+ * Create a response indicating an error.
  *
  * @param ec error code to return
  * @param hint hint about the internal error's nature
  * @return a MHD response object
  */
 struct MHD_Response *
-TMH_RESPONSE_make_external_error (enum TALER_ErrorCode ec,
-                                 const char *hint);
+TMH_RESPONSE_make_error (enum TALER_ErrorCode ec,
+                        const char *hint);
 
 
 /**
diff --git a/src/backend/taler-merchant-httpd_track-transaction.c 
b/src/backend/taler-merchant-httpd_track-transaction.c
index c60daf6..27d2d06 100644
--- a/src/backend/taler-merchant-httpd_track-transaction.c
+++ b/src/backend/taler-merchant-httpd_track-transaction.c
@@ -640,8 +640,8 @@ wtid_cb (void *cls,
     resume_track_transaction_with_response
       (tcc->tctx,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
-       TMH_RESPONSE_make_internal_error 
(TALER_EC_TRACK_TRANSACTION_DB_FETCH_FAILED,
-                                        "Fail to query database about 
proofs"));
+       TMH_RESPONSE_make_error (TALER_EC_TRACK_TRANSACTION_DB_FETCH_FAILED,
+                               "Fail to query database about proofs"));
     return;
   }
   /* WARNING: if two transactions got aggregated under the same
@@ -878,7 +878,7 @@ handle_track_transaction_timeout (void *cls)
   }
   resume_track_transaction_with_response (tctx,
                                           MHD_HTTP_SERVICE_UNAVAILABLE,
-                                          TMH_RESPONSE_make_internal_error 
(TALER_EC_PAY_EXCHANGE_TIMEOUT,
+                                          TMH_RESPONSE_make_error 
(TALER_EC_PAY_EXCHANGE_TIMEOUT,
                                                                            
"exchange not reachable"));
 }
 
diff --git a/src/backend/taler-merchant-httpd_track-transfer.c 
b/src/backend/taler-merchant-httpd_track-transfer.c
index 0e84b1d..3d4b418 100644
--- a/src/backend/taler-merchant-httpd_track-transfer.c
+++ b/src/backend/taler-merchant-httpd_track-transfer.c
@@ -750,8 +750,8 @@ wire_transfer_cb (void *cls,
     resume_track_transfer_with_response
       (rctx,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
-       TMH_RESPONSE_make_internal_error 
(TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR,
-                                         "Fail to elaborate the response."));
+       TMH_RESPONSE_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR,
+                               "Fail to elaborate the response."));
     return;
   }
 
@@ -818,7 +818,7 @@ handle_track_transfer_timeout (void *cls)
   }
   resume_track_transfer_with_response (rctx,
                                        MHD_HTTP_SERVICE_UNAVAILABLE,
-                                       TMH_RESPONSE_make_internal_error 
(TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT,
+                                       TMH_RESPONSE_make_error 
(TALER_EC_TRACK_TRANSFER_EXCHANGE_TIMEOUT,
                                                                         
"exchange not reachable"));
 }
 
@@ -845,8 +845,8 @@ proof_cb (void *cls,
   {
     rctx->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
     rctx->response
-      = TMH_RESPONSE_make_internal_error 
(TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR,
-                                         "Fail to elaborate response.");
+      = TMH_RESPONSE_make_error (TALER_EC_TRACK_TRANSFER_JSON_RESPONSE_ERROR,
+                                "Fail to elaborate response.");
     return;
   }
 
diff --git a/src/include/taler_merchant_service.h 
b/src/include/taler_merchant_service.h
index e99024a..a3fca0f 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -55,7 +55,7 @@ typedef void
  * Does a GET /refund.
  *
  * @param ctx execution context
- * @param backend_uri base URL of the merchant backend
+ * @param backend_url base URL of the merchant backend
  * @param order_id order id used to perform the lookup
  * @param cb callback which will work the response gotten from the backend
  * @param cb_cls closure to pass to the callback
@@ -63,7 +63,7 @@ typedef void
  */
 struct TALER_MERCHANT_RefundLookupOperation *
 TALER_MERCHANT_refund_lookup (struct GNUNET_CURL_Context *ctx,
-                              const char *backend_uri,
+                              const char *backend_url,
                               const char *order_id,
                               const char *instance,
                               TALER_MERCHANT_RefundLookupCallback cb,
@@ -103,7 +103,7 @@ typedef void
  * Increase the refund associated to a order
  *
  * @param ctx the CURL context used to connect to the backend
- * @param backend_uri backend's base URL, including final "/"
+ * @param backend_url backend's base URL, including final "/"
  * @param order_id id of the order whose refund is to be increased
  * @param refund amount to which increase the refund
  * @param reason human-readable reason justifying the refund
@@ -113,7 +113,7 @@ typedef void
  */
 struct TALER_MERCHANT_RefundIncreaseOperation *
 TALER_MERCHANT_refund_increase (struct GNUNET_CURL_Context *ctx,
-                                const char *backend_uri,
+                                const char *backend_url,
                                 const char *order_id,
                                 const struct TALER_Amount *refund,
                                 const char *reason,
@@ -165,7 +165,7 @@ typedef void
  * PUT an order to the backend and receives the related proposal.
  *
  * @param ctx execution context
- * @param backend_uri URI of the backend
+ * @param backend_url URL of the backend
  * @param order basic information about this purchase, to be extended by the
  * backend
  * @param proposal_cb the callback to call when a reply for this request is 
available
@@ -174,7 +174,7 @@ typedef void
  */
 struct TALER_MERCHANT_ProposalOperation *
 TALER_MERCHANT_order_put (struct GNUNET_CURL_Context *ctx,
-                          const char *backend_uri,
+                          const char *backend_url,
                           const json_t *order,
                           TALER_MERCHANT_ProposalCallback proposal_cb,
                           void *proposal_cb_cls);
@@ -215,7 +215,7 @@ typedef void
  * retrieve a proposal data by providing its transaction id.
  *
  * @param ctx execution context
- * @param backend_uri base URL of the merchant backend
+ * @param backend_url base URL of the merchant backend
  * @param transaction_id transaction id used to perform the lookup
  * @param plo_cb callback which will work the response gotten from the backend
  * @param plo_cb_cls closure to pass to @a history_cb
@@ -223,7 +223,7 @@ typedef void
  */
 struct TALER_MERCHANT_ProposalLookupOperation *
 TALER_MERCHANT_proposal_lookup (struct GNUNET_CURL_Context *ctx,
-                                const char *backend_uri,
+                                const char *backend_url,
                                 const char *transaction_id,
                                 const char *instance,
                                 TALER_MERCHANT_ProposalLookupOperationCallback 
plo_cb,
@@ -249,7 +249,7 @@ TALER_MERCHANT_proposal_lookup_cancel (struct 
TALER_MERCHANT_ProposalLookupOpera
  * or backends (API for frontends).  The difference is that for the
  * frontend API, we need the private keys of the coins, while for
  * the backend API we need the public keys and signatures received
- * from the wallet.  Also, the frontend returns a redirect URI on
+ * from the wallet.  Also, the frontend returns a redirect URL on
  * success, while the backend just returns a success status code.
  */
 struct TALER_MERCHANT_Pay;
@@ -310,6 +310,11 @@ struct TALER_MERCHANT_PayCoin
    */
   struct TALER_Amount amount_without_fee;
 
+  /**
+   * URL of the exchange that issued @e coin_priv.
+   */ 
+  const char *exchange_url;
+
 };
 
 
@@ -317,7 +322,7 @@ struct TALER_MERCHANT_PayCoin
  * Pay a merchant.  API for wallets that have the coin's private keys.
  *
  * @param ctx execution context
- * @param merchant_uri base URI of the merchant
+ * @param merchant_url base URL of the merchant
  * @param instance which merchant instance will receive this payment
  * @param h_wire hash of the merchant’s account details
  * @param h_contract hash of the contact of the merchant with the customer
@@ -329,7 +334,6 @@ struct TALER_MERCHANT_PayCoin
  * @param timestamp timestamp when the contract was finalized, must match 
approximately the current time of the merchant
  * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the merchant (can be zero if refunds are not allowed)
  * @param pay_deadline maximum time limit to pay for this contract
- * @param exchange_uri URI of the exchange that the coins belong to
  * @param num_coins number of coins used to pay
  * @param coins array of coins we use to pay
  * @param coin_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s 
private key.
@@ -339,7 +343,7 @@ struct TALER_MERCHANT_PayCoin
  */
 struct TALER_MERCHANT_Pay *
 TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
-                          const char *merchant_uri,
+                          const char *merchant_url,
                           const char *instance,
                            const struct GNUNET_HashCode *h_contract,
                            const struct TALER_Amount *amount,
@@ -350,7 +354,6 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
                            struct GNUNET_TIME_Absolute refund_deadline,
                            struct GNUNET_TIME_Absolute pay_deadline,
                            const struct GNUNET_HashCode *h_wire,
-                          const char *exchange_uri,
                            const char *order_id,
                            unsigned int num_coins,
                            const struct TALER_MERCHANT_PayCoin *coins,
@@ -399,6 +402,11 @@ struct TALER_MERCHANT_PaidCoin
    */
   struct TALER_Amount amount_without_fee;
 
+  /**
+   * What is the URL of the exchange that issued @a coin_pub?
+   */
+  const char *exchange_url;
+
 };
 
 
@@ -409,7 +417,7 @@ struct TALER_MERCHANT_PaidCoin
  * in the type of @a coins compared to #TALER_MERCHANT_pay().
  *
  * @param ctx execution context
- * @param merchant_uri base URI of the merchant
+ * @param merchant_url base URL of the merchant
  * @param instance which merchant instance will receive this payment
  * @param h_contract hash of the contact of the merchant with the customer
  * @param amount total value of the contract to be paid to the merchant
@@ -420,7 +428,6 @@ struct TALER_MERCHANT_PaidCoin
  * @param pay_deadline maximum time limit to pay for this contract
  * @param timestamp timestamp when the contract was finalized, must match 
approximately the current time of the merchant
  * @param wire_transfer_deadline date by which the merchant would like the 
exchange to execute the wire transfer (can be zero if there is no specific date 
desired by the frontend). If non-zero, must be larger than @a refund_deadline.
- * @param exchange_uri URI of the exchange that the coins belong to
  * @param num_coins number of coins used to pay
  * @param coins array of coins we use to pay
  * @param coin_sig the signature made with purpose 
#TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s 
private key.
@@ -430,10 +437,9 @@ struct TALER_MERCHANT_PaidCoin
  */
 struct TALER_MERCHANT_Pay *
 TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context *ctx,
-                            const char *merchant_uri,
+                            const char *merchant_url,
                              const struct TALER_MerchantPublicKeyP 
*merchant_pub,
                             const char *order_id,
-                            const char *exchange_uri,
                              unsigned int num_coins,
                              const struct TALER_MERCHANT_PaidCoin *coins,
                              TALER_MERCHANT_PayCallback pay_cb,
@@ -517,7 +523,7 @@ typedef void
  * Request backend to return deposits associated with a given wtid.
  *
  * @param ctx execution context
- * @param backend_uri base URI of the backend
+ * @param backend_url base URL of the backend
  * @param instance which merchant instance is going to be tracked
  * @param wire_method wire method used for the wire transfer
  * @param wtid base32 string indicating a wtid
@@ -528,11 +534,11 @@ typedef void
  */
 struct TALER_MERCHANT_TrackTransferHandle *
 TALER_MERCHANT_track_transfer (struct GNUNET_CURL_Context *ctx,
-                               const char *backend_uri,
+                               const char *backend_url,
                                const char *instance,
                               const char *wire_method,
                                const struct TALER_WireTransferIdentifierRawP 
*wtid,
-                               const char *exchange_uri,
+                               const char *exchange_url,
                                TALER_MERCHANT_TrackTransferCallback 
track_transfer_cb,
                                void *track_transfer_cb_cls);
 
@@ -578,40 +584,6 @@ struct TALER_MERCHANT_CoinWireTransfer
 
 };
 
-/**
- * Information about a wire transfer for a /track/transaction response.
- */
-struct TALER_MERCHANT_TransactionWireTransfer
-{
-
-  /**
-   * Wire transfer identifier this struct is about.
-   */
-  struct TALER_WireTransferIdentifierRawP wtid;
-
-  /**
-   * When was this wire transfer executed?
-   */
-  struct GNUNET_TIME_Absolute execution_time;
-
-  /**
-   * Number of coins of the selected transaction that
-   * is covered by this wire transfer.
-   */
-  unsigned int num_coins;
-
-  /**
-   * Information about the coins of the selected transaction
-   * that are part of the wire transfer.
-   */
-  struct TALER_MERCHANT_CoinWireTransfer *coins;
-
-  /**
-   * URL of the exchange that executed the wire transfer.
-   */
-  char *exchange_url;
-};
-
 
 /**
  * Callbacks of this type are used to work the result of submitting a
@@ -635,7 +607,7 @@ typedef void
  * Request backend to return deposits associated with a given wtid.
  *
  * @param ctx execution context
- * @param backend_uri base URI of the backend
+ * @param backend_url base URL of the backend
  * @param instance which merchant instance is going to be tracked
  * @param transaction_id which transaction should we trace
  * @param track_transaction_cb the callback to call when a reply for this 
request is available
@@ -644,7 +616,7 @@ typedef void
  */
 struct TALER_MERCHANT_TrackTransactionHandle *
 TALER_MERCHANT_track_transaction (struct GNUNET_CURL_Context *ctx,
-                                  const char *backend_uri,
+                                  const char *backend_url,
                                   const char *instance,
                                   const char *order_id,
                                   TALER_MERCHANT_TrackTransactionCallback 
track_transaction_cb,
@@ -684,7 +656,7 @@ typedef void
  * Issue a /history request to the backend.
  *
  * @param ctx execution context
- * @param backend_uri base URL of the merchant backend
+ * @param backend_url base URL of the merchant backend
  * @param instance which merchant instance is performing this call
  * @param start return @a delta records starting from position @a start
  * @param delta return @a delta records starting from position @a start
@@ -695,7 +667,7 @@ typedef void
  */
 struct TALER_MERCHANT_HistoryOperation *
 TALER_MERCHANT_history (struct GNUNET_CURL_Context *ctx,
-                        const char *backend_uri,
+                        const char *backend_url,
                         const char *instance,
                         unsigned int start,
                         unsigned int delta,
@@ -741,7 +713,7 @@ typedef void
  * Issue a /tip-enable request to the backend.  Informs the backend
  * that a reserve is now available for tipping.  Note that the
  * respective @a reserve_priv must also be bound to one or more
- * instances (together with the URI of the exchange) via the backend's
+ * instances (together with the URL of the exchange) via the backend's
  * configuration file before it can be used.  Usually, the process
  * is that one first configures an exchange and a @a reserve_priv for
  * an instance, and then enables (or re-enables) the reserve by
@@ -749,7 +721,7 @@ typedef void
  * this API.
  *
  * @param ctx execution context
- * @param backend_uri base URL of the merchant backend
+ * @param backend_url base URL of the merchant backend
  * @param amount amount that was credited to the reserve
  * @param expiration when will the reserve expire
  * @param reserve_priv private key of the reserve
@@ -760,7 +732,7 @@ typedef void
  */
 struct TALER_MERCHANT_TipEnableOperation *
 TALER_MERCHANT_tip_enable (struct GNUNET_CURL_Context *ctx,
-                           const char *backend_uri,
+                           const char *backend_url,
                            const struct TALER_Amount *amount,
                            struct GNUNET_TIME_Absolute expiration,
                            const struct TALER_ReservePrivateKeyP *reserve_priv,
@@ -796,7 +768,7 @@ struct TALER_MERCHANT_TipAuthorizeOperation;
  * @param ec taler-specific error code
  * @param tip_id which tip ID should be used to pickup the tip
  * @param tip_expiration when does the tip expire (needs to be picked up 
before this time)
- * @param exchange_uri at what exchange can the tip be picked up
+ * @param exchange_url at what exchange can the tip be picked up
  */
 typedef void
 (*TALER_MERCHANT_TipAuthorizeCallback) (void *cls,
@@ -804,7 +776,7 @@ typedef void
                                         enum TALER_ErrorCode ec,
                                         const struct GNUNET_HashCode *tip_id,
                                         struct GNUNET_TIME_Absolute 
tip_expiration,
-                                        const char *exchange_uri);
+                                        const char *exchange_url);
 
 
 /**
@@ -879,7 +851,7 @@ typedef void
  * that a customer wants to pick up a tip.
  *
  * @param ctx execution context
- * @param backend_uri base URL of the merchant backend
+ * @param backend_url base URL of the merchant backend
  * @param tip_id unique identifier for the tip
  * @param num_planches number of planchets provided in @a planchets
  * @param planchets array of planchets to be signed into existence for the tip
@@ -889,7 +861,7 @@ typedef void
  */
 struct TALER_MERCHANT_TipPickupOperation *
 TALER_MERCHANT_tip_pickup (struct GNUNET_CURL_Context *ctx,
-                           const char *backend_uri,
+                           const char *backend_url,
                            const struct GNUNET_HashCode *tip_id,
                            unsigned int num_planchets,
                            struct TALER_PlanchetDetail *planchets,
diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c
index a480061..bfb1ae8 100644
--- a/src/lib/merchant_api_pay.c
+++ b/src/lib/merchant_api_pay.c
@@ -265,7 +265,6 @@ handle_pay_finished (void *cls,
  * @param refund_deadline date until which the merchant can issue a refund to 
the customer via the merchant (can be zero if refunds are not allowed)
  * @param pay_deadline maximum time limit to pay for this contract
  * @param h_wire hash of the merchant’s account details
- * @param exchange_uri URI of the exchange that the coins belong to
  * @param order_id order id of the proposal being paid
  * @param num_coins number of coins used to pay
  * @param coins array of coins we use to pay
@@ -286,14 +285,12 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context 
*ctx,
                            struct GNUNET_TIME_Absolute refund_deadline,
                            struct GNUNET_TIME_Absolute pay_deadline,
                            const struct GNUNET_HashCode *h_wire,
-                          const char *exchange_uri,
                            const char *order_id,
                            unsigned int num_coins,
                            const struct TALER_MERCHANT_PayCoin *coins,
                            TALER_MERCHANT_PayCallback pay_cb,
                            void *pay_cb_cls)
 {
-  unsigned int i;
   struct TALER_DepositRequestPS dr;
   struct TALER_MERCHANT_PaidCoin pc[num_coins];
 
@@ -316,7 +313,7 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
   dr.timestamp = GNUNET_TIME_absolute_hton (timestamp);
   dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline);
   dr.merchant = *merchant_pub;
-  for (i=0;i<num_coins;i++)
+  for (unsigned int i=0;i<num_coins;i++)
   {
     const struct TALER_MERCHANT_PayCoin *coin = &coins[i];
     struct TALER_MERCHANT_PaidCoin *p = &pc[i];
@@ -355,12 +352,12 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context 
*ctx,
     p->coin_pub = dr.coin_pub;
     p->amount_with_fee = coin->amount_with_fee;
     p->amount_without_fee = coin->amount_without_fee;
+    p->exchange_url = coin->exchange_url;
   }
   return TALER_MERCHANT_pay_frontend (ctx,
                                      merchant_uri,
                                       merchant_pub,
                                       order_id,
-                                     exchange_uri,
                                      num_coins,
                                      pc,
                                      pay_cb,
@@ -377,7 +374,6 @@ TALER_MERCHANT_pay_wallet (struct GNUNET_CURL_Context *ctx,
  * @param ctx the execution loop context
  * @param merchant_uri base URI of the merchant's backend
  * @param merchant_pub public key of the merchant
- * @param exchange_uri URI of the exchange that the coins belong to
  * @param num_coins number of coins used to pay
  * @param coins array of coins we use to pay
  * @param pay_cb the callback to call when a reply for this request is 
available
@@ -389,7 +385,6 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context 
*ctx,
                             const char *merchant_uri,
                              const struct TALER_MerchantPublicKeyP 
*merchant_pub,
                              const char *order_id,
-                            const char *exchange_uri,
                              unsigned int num_coins,
                              const struct TALER_MERCHANT_PaidCoin *coins,
                              TALER_MERCHANT_PayCallback pay_cb,
@@ -449,11 +444,12 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context 
*ctx,
     }
 
     /* create JSON for this coin */
-    j_coin = json_pack ("{s:o, s:o," /* f/coin_pub */
-                       " s:o, s:o," /* denom_pub / ub_sig */
-                       " s:o}",     /* coin_sig */
-                       "f", TALER_JSON_from_amount (&pc->amount_with_fee),
+    j_coin = json_pack ("{s:o, s:o," /* contribution/coin_pub */
+                       " s:s, s:o," /* exchange_url / denom_pub */
+                       " s:o, s:o}", /* ub_sig / coin_sig */
+                       "contribution", TALER_JSON_from_amount 
(&pc->amount_with_fee),
                        "coin_pub", GNUNET_JSON_from_data_auto (&pc->coin_pub),
+                       "exchange_url", pc->exchange_url,
                        "denom_pub", GNUNET_JSON_from_rsa_public_key 
(pc->denom_pub.rsa_public_key),
                        "ub_sig", GNUNET_JSON_from_rsa_signature 
(pc->denom_sig.rsa_signature),
                        "coin_sig", GNUNET_JSON_from_data_auto (&pc->coin_sig)
@@ -469,12 +465,10 @@ TALER_MERCHANT_pay_frontend (struct GNUNET_CURL_Context 
*ctx,
   }
 
   pay_obj = json_pack ("{"
-                       " s:s," /* exchange */
                        " s:o," /* coins */
                        " s:s," /* order_id */
                        " s:o," /* merchant_pub */
                        "}",
-                      "exchange", exchange_uri,
                       "coins", j_coins,
                        "order_id", order_id,
                        "merchant_pub", GNUNET_JSON_from_data_auto 
(merchant_pub));
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
index 769421b..941f46b 100644
--- a/src/lib/test_merchant_api.c
+++ b/src/lib/test_merchant_api.c
@@ -2696,12 +2696,14 @@ interpreter_run (void *cls)
           icoin->denom_pub = coin_ref->details.reserve_withdraw.pk->key;
           icoin->denom_sig = coin_ref->details.reserve_withdraw.sig;
           icoin->denom_value = coin_ref->details.reserve_withdraw.pk->value;
+         icoin->exchange_url = EXCHANGE_URL;
           break;
         case OC_TIP_PICKUP:
           icoin->coin_priv = coin_ref->details.tip_pickup.psa[ci].coin_priv;
           icoin->denom_pub = coin_ref->details.tip_pickup.dks[ci]->key;
           icoin->denom_sig = coin_ref->details.tip_pickup.sigs[ci];
           icoin->denom_value = coin_ref->details.tip_pickup.dks[ci]->value;
+         icoin->exchange_url = EXCHANGE_URL;
           break;
         default:
           GNUNET_assert (0);
@@ -2729,7 +2731,6 @@ interpreter_run (void *cls)
          refund_deadline,
          pay_deadline,
          &h_wire,
-         EXCHANGE_URL,
          order_id,
          npc /* num_coins */,
          pc /* coins */,
diff --git a/src/merchant-tools/taler-merchant-generate-payments.c 
b/src/merchant-tools/taler-merchant-generate-payments.c
index 4ba257f..887b9db 100644
--- a/src/merchant-tools/taler-merchant-generate-payments.c
+++ b/src/merchant-tools/taler-merchant-generate-payments.c
@@ -1167,6 +1167,7 @@ interpreter_run (void *cls)
             pc.denom_pub = coin_ref->details.reserve_withdraw.pk->key;
             pc.denom_sig = coin_ref->details.reserve_withdraw.sig;
             pc.denom_value = coin_ref->details.reserve_withdraw.pk->value;
+           pc.exchange_url = exchange_url;
             break;
           default:
             GNUNET_assert (0);
@@ -1209,7 +1210,6 @@ interpreter_run (void *cls)
                                        refund_deadline,
                                        pay_deadline,
                                        &h_wire,
-                                       exchange_url,
                                        order_id,
                                        1 /* num_coins */,
                                        &pc /* coins */,

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



reply via email to

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