gnunet-svn
[Top][All Lists]
Advanced

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

[GNUnet-SVN] [taler-merchant] 02/02: working on #5158, moving exchange_u


From: gnunet
Subject: [GNUnet-SVN] [taler-merchant] 02/02: working on #5158, moving exchange_url to coin in /pay API, also renaming 'f' field to more clear 'contribution'
Date: Tue, 26 Dec 2017 21:43:26 +0100

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

grothoff pushed a commit to branch master
in repository merchant.

commit bc88e6bf6a92d79d15ec41254c85ac4550a38ba9
Author: Christian Grothoff <address@hidden>
AuthorDate: Tue Dec 26 21:43:20 2017 +0100

    working on #5158, moving exchange_url to coin in /pay API, also renaming 
'f' field to more clear 'contribution'
---
 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               |   68 +-
 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(+), 569 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 d6b3260..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);
 
@@ -601,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
@@ -610,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,
@@ -650,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
@@ -661,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,
@@ -707,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
@@ -715,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
@@ -726,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,
@@ -762,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,
@@ -770,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);
 
 
 /**
@@ -845,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
@@ -855,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]