gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] branch master updated: major /pay handling cleanup


From: gnunet
Subject: [taler-merchant] branch master updated: major /pay handling cleanup
Date: Mon, 27 Nov 2023 05:10:37 +0100

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

grothoff pushed a commit to branch master
in repository merchant.

The following commit(s) were added to refs/heads/master by this push:
     new 5f97f911 major /pay handling cleanup
5f97f911 is described below

commit 5f97f9112746afa308d6b281cbefad4b6e166370
Author: Christian Grothoff <grothoff@gnunet.org>
AuthorDate: Mon Nov 27 13:10:30 2023 +0900

    major /pay handling cleanup
---
 .../taler-merchant-httpd_post-orders-ID-pay.c      | 1502 +++++++++++---------
 1 file changed, 833 insertions(+), 669 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c 
b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
index c942847f..e263538a 100644
--- a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
+++ b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -58,6 +58,63 @@
 struct PayContext;
 
 
+/**
+ * Different phases of processing the /pay request.
+ */
+enum PayPhase
+{
+  /**
+   * Initial phase where the request is parsed.
+   */
+  PP_INIT = 0,
+
+  /**
+   * Check database state for the given order.
+   */
+  PP_CHECK_CONTRACT,
+
+  /**
+   * Contract has been paid.
+   */
+  PP_CONTRACT_PAID,
+
+  /**
+   * Execute payment transaction.
+   */
+  PP_PAY_TRANSACTION,
+
+  /**
+   * Notify other processes about successful payment.
+   */
+  PP_PAYMENT_NOTIFICATION,
+
+  /**
+   * Create final success response.
+   */
+  PP_SUCCESS_RESPONSE,
+
+  /**
+   * Perform batch deposits with exchange(s).
+   */ 
+  PP_BATCH_DEPOSITS,
+
+  /**
+   * Return response in payment context.
+   */
+  PP_RETURN_RESPONSE,
+  
+  /**
+   * Return #MHD_YES to end processing.
+   */
+  PP_END_YES,
+
+  /**
+   * Return #MHD_NO to end processing.
+   */
+  PP_END_NO
+};
+  
+
 /**
  * Information kept during a pay request for each coin.
  */
@@ -228,7 +285,7 @@ struct PayContext
 
   /**
    * What wire method (of the @e mi) was selected by the wallet?
-   * Set in #parse_pay().
+   * Set in #phase_parse_pay().
    */
   struct TMH_WireMethod *wm;
 
@@ -391,6 +448,11 @@ struct PayContext
    */
   unsigned int response_code;
 
+  /**
+   * Payment processing phase we are in.
+   */ 
+  enum PayPhase phase;
+  
   /**
    * #GNUNET_NO if the @e connection was not suspended,
    * #GNUNET_YES if the @e connection was suspended,
@@ -404,13 +466,13 @@ struct PayContext
    * does not match the contract currency.
    */
   bool deposit_currency_mismatch;
-  
+
   /**
    * Set to true if the database contains a (bogus)
    * refund for a different currency.
    */
   bool refund_currency_mismatch;
-  
+
 };
 
 
@@ -526,29 +588,6 @@ destroy_kc (struct KycContext *kc)
 }
 
 
-/**
- * Compute the timeout for a /pay request based on the number of coins
- * involved.
- *
- * @param num_coins number of coins
- * @returns timeout for the /pay request
- */
-static struct GNUNET_TIME_Relative
-get_pay_timeout (unsigned int num_coins)
-{
-  struct GNUNET_TIME_Relative t;
-
-  /* FIXME:  Do some benchmarking to come up with a better timeout.
-   * We've increased this value so the wallet integration test passes again
-   * on my (Florian) machine.
-   */
-  t = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
-                                     15 * (1 + (num_coins / 5)));
-
-  return t;
-}
-
-
 void
 TMH_force_pc_resume ()
 {
@@ -579,6 +618,21 @@ TMH_force_pc_resume ()
 }
 
 
+/**
+ * Resume payment processing.
+ *
+ * @param[in,out] pc payment process to resume
+ */
+static void
+pay_resume (struct PayContext *pc)
+{
+  GNUNET_assert (GNUNET_YES == pc->suspended);
+  pc->suspended = GNUNET_NO;
+  MHD_resume_connection (pc->connection);
+  TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+}
+
+
 /**
  * Resume the given pay context and send the given response.
  * Stores the response in the @a pc and signals MHD to resume
@@ -603,10 +657,8 @@ resume_pay_with_response (struct PayContext *pc,
     GNUNET_SCHEDULER_cancel (pc->timeout_task);
     pc->timeout_task = NULL;
   }
-  GNUNET_assert (GNUNET_YES == pc->suspended);
-  pc->suspended = GNUNET_NO;
-  MHD_resume_connection (pc->connection);
-  TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+  pc->phase = PP_RETURN_RESPONSE;
+  pay_resume (pc);
 }
 
 
@@ -631,66 +683,44 @@ resume_pay_with_error (struct PayContext *pc,
 
 
 /**
- * Custom cleanup routine for a `struct PayContext`.
+ * Conclude payment processing for @a pc with the
+ * given @a res MHD status code.
  *
- * @param cls the `struct PayContext` to clean up.
+ * @param[in,out] pc payment context for final state transition
+ * @param res MHD return code to end with
  */
 static void
-pay_context_cleanup (void *cls)
+pay_end (struct PayContext *pc,
+         MHD_RESULT res)
 {
-  struct PayContext *pc = cls;
-
-  if (NULL != pc->timeout_task)
-  {
-    GNUNET_SCHEDULER_cancel (pc->timeout_task);
-    pc->timeout_task = NULL;
-  }
-  if (NULL != pc->contract_terms)
-  {
-    json_decref (pc->contract_terms);
-    pc->contract_terms = NULL;
-  }
-  for (unsigned int i = 0; i<pc->coins_cnt; i++)
-  {
-    struct DepositConfirmation *dc = &pc->dc[i];
-
-    TALER_denom_sig_free (&dc->cdd.denom_sig);
-    GNUNET_free (dc->exchange_url);
-  }
-  GNUNET_free (pc->dc);
-  for (unsigned int i = 0; i<pc->num_exchanges; i++)
-  {
-    struct ExchangeGroup *eg = pc->egs[i];
-
-    if (NULL != eg->fo)
-      TMH_EXCHANGES_keys4exchange_cancel (eg->fo);
-    GNUNET_free (eg);
-  }
-  GNUNET_free (pc->egs);
-  if (NULL != pc->response)
-  {
-    MHD_destroy_response (pc->response);
-    pc->response = NULL;
-  }
-  GNUNET_free (pc->fulfillment_url);
-  GNUNET_free (pc->session_id);
-  GNUNET_CONTAINER_DLL_remove (pc_head,
-                               pc_tail,
-                               pc);
-  GNUNET_free (pc->pos_key);
-  GNUNET_free (pc);
+  pc->phase = (MHD_YES == res)
+    ? PP_END_YES
+    : PP_END_NO;
 }
 
 
 /**
- * Execute the DB transaction.  If required (from
- * soft/serialization errors), the transaction can be
- * restarted here.
+ * Return response stored in @a pc.
  *
- * @param pc payment context to transact
+ * @param[in,out] pc payment context we are processing
  */
 static void
-execute_pay_transaction (struct PayContext *pc);
+phase_return_response (struct PayContext *pc)
+{
+  GNUNET_assert (0 != pc->response_code);
+  /* We are *done* processing the request, just queue the response (!) */
+  if (UINT_MAX == pc->response_code)
+  {
+    GNUNET_break (0);
+    pay_end (pc,
+             MHD_NO); /* hard error */
+    return;
+  }
+  pay_end (pc,
+           MHD_queue_response (pc->connection,
+                               pc->response_code,
+                               pc->response));
+}
 
 
 /**
@@ -1113,7 +1143,10 @@ batch_deposit_cb (
     handle_batch_deposit_ok (eg,
                              dr);
     if (0 == pc->pending_at_eg)
-      execute_pay_transaction (eg->pc);
+    {
+      pc->phase = PP_PAY_TRANSACTION;
+      pay_resume (pc);
+    }
     return;
   default:
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -1232,23 +1265,7 @@ process_pay_with_keys (
         NULL);
       return;
     }
-    eg->tried_force_keys = true;
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Forcing /keys download (once) as wire method seems 
unsupported for debit\n");
-    eg->fo = TMH_EXCHANGES_keys4exchange (
-      eg->exchange_url,
-      true,
-      &process_pay_with_keys,
-      eg);
-    if (NULL == eg->fo)
-    {
-      GNUNET_break (0);
-      pc->pending_at_eg--;
-      resume_pay_with_error (pc,
-                             
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED,
-                             "Failed to lookup exchange by URL");
-      return;
-    }
+    force_keys (eg);
     return;
   }
 
@@ -1421,7 +1438,11 @@ AGE_FAIL:
                 "Group size zero, %u batch transactions remain pending\n",
                 pc->pending_at_eg);
     if (0 == pc->pending_at_eg)
-      execute_pay_transaction (pc);
+    {
+      pc->phase = PP_PAY_TRANSACTION;
+      pay_resume (pc);
+      return;
+    }
     return;
   }
 
@@ -1509,14 +1530,57 @@ force_keys (struct ExchangeGroup *eg)
 }
 
 
+/**
+ * Handle a timeout for the processing of the pay request.
+ *
+ * @param cls our `struct PayContext`
+ */
+static void
+handle_pay_timeout (void *cls)
+{
+  struct PayContext *pc = cls;
+
+  pc->timeout_task = NULL;
+  GNUNET_assert (GNUNET_YES == pc->suspended);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Resuming pay with error after timeout\n");
+  resume_pay_with_error (pc,
+                         TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
+                         NULL);
+}
+
+
+/**
+ * Compute the timeout for a /pay request based on the number of coins
+ * involved.
+ *
+ * @param num_coins number of coins
+ * @returns timeout for the /pay request
+ */
+static struct GNUNET_TIME_Relative
+get_pay_timeout (unsigned int num_coins)
+{
+  struct GNUNET_TIME_Relative t;
+
+  /* FIXME:  Do some benchmarking to come up with a better timeout.
+   * We've increased this value so the wallet integration test passes again
+   * on my (Florian) machine.
+   */
+  t = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
+                                     15 * (1 + (num_coins / 5)));
+
+  return t;
+}
+
+
 /**
  * Start batch deposits for all exchanges involved
  * in this payment.
  *
- * @param pc payment context we are processing
+ * @param[in,out] pc payment context we are processing
  */
 static void
-start_batch_deposits (struct PayContext *pc)
+phase_batch_deposits (struct PayContext *pc)
 {
   for (unsigned int i = 0; i<pc->num_exchanges; i++)
   {
@@ -1548,45 +1612,150 @@ start_batch_deposits (struct PayContext *pc)
     if (NULL == eg->fo)
     {
       GNUNET_break (0);
-      resume_pay_with_error (pc,
-                             
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED,
-                             "Failed to lookup exchange by URL");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (pc->connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LOOKUP_FAILED,
+                                           "Failed to lookup exchange by 
URL"));
       return;
     }
     pc->pending_at_eg++;
   }
   if (0 == pc->pending_at_eg)
-    execute_pay_transaction (pc);
+  {
+    pc->phase = PP_PAY_TRANSACTION;
+    pay_resume (pc);
+    return;
+  }
+  /* Suspend while we interact with the exchange */
+  MHD_suspend_connection (pc->connection);
+  pc->suspended = GNUNET_YES;
+  GNUNET_assert (NULL == pc->timeout_task);
+  pc->timeout_task
+    = GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt),
+                                    &handle_pay_timeout,
+                                    pc);
 }
 
 
 /**
- * Function called with information about a coin that was deposited.
+ * Generate response (payment successful)
  *
- * @param cls closure
- * @param exchange_url exchange where @a coin_pub was deposited
- * @param coin_pub public key of the coin
- * @param amount_with_fee amount the exchange will deposit for this coin
- * @param deposit_fee fee the exchange will charge for this coin
- * @param refund_fee fee the exchange will charge for refunding this coin
+ * @param[in,out] pc payment context where the payment was successful
  */
 static void
-check_coin_paid (void *cls,
-                 const char *exchange_url,
-                 const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                 const struct TALER_Amount *amount_with_fee,
-                 const struct TALER_Amount *deposit_fee,
-                 const struct TALER_Amount *refund_fee)
+phase_success_response (struct PayContext *pc)
 {
-  struct PayContext *pc = cls;
+  struct GNUNET_CRYPTO_EddsaSignature sig;
+  char *pos_confirmation;
 
-  for (unsigned int i = 0; i<pc->coins_cnt; i++)
+  /* Sign on our end (as the payment did go through, even if it may
+     have been refunded already) */
+  TALER_merchant_pay_sign (&pc->h_contract_terms,
+                           &pc->hc->instance->merchant_priv,
+                           &sig);
+  /* Build the response */
+  pos_confirmation = (NULL == pc->pos_key)
+    ? NULL
+    : TALER_build_pos_confirmation (pc->pos_key,
+                                    pc->pos_alg,
+                                    &pc->amount,
+                                    pc->timestamp);
+  pay_end (pc,
+           TALER_MHD_REPLY_JSON_PACK (
+             pc->connection,
+             MHD_HTTP_OK,
+             GNUNET_JSON_pack_allow_null (
+               GNUNET_JSON_pack_string ("pos_confirmation",
+                                        pos_confirmation)),
+               GNUNET_JSON_pack_data_auto ("sig",
+                                           &sig)));
+  GNUNET_free (pos_confirmation);
+}
+
+
+/**
+ * Use database to notify other clients about the
+ * payment being completed.
+ *
+ * @param[in,out] pc context to trigger notification for
+ */
+static void
+phase_payment_notification (struct PayContext *pc)
+{
   {
-    struct DepositConfirmation *dc = &pc->dc[i];
+    struct TMH_OrderPayEventP pay_eh = {
+      .header.size = htons (sizeof (pay_eh)),
+      .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
+      .merchant_pub = pc->hc->instance->merchant_pub
+    };
 
-    if (dc->found_in_db)
-      continue; /* processed earlier, skip "expensive" memcmp() */
-    /* Get matching coin from results*/
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Notifying clients about payment of order %s\n",
+                pc->order_id);
+    GNUNET_CRYPTO_hash (pc->order_id,
+                        strlen (pc->order_id),
+                        &pay_eh.h_order_id);
+    TMH_db->event_notify (TMH_db->cls,
+                          &pay_eh.header,
+                          NULL,
+                          0);
+  }
+  if ( (NULL != pc->session_id) &&
+       (NULL != pc->fulfillment_url) )
+  {
+    struct TMH_SessionEventP session_eh = {
+      .header.size = htons (sizeof (session_eh)),
+      .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
+      .merchant_pub = pc->hc->instance->merchant_pub
+    };
+
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Notifying clients about session change to %s for %s\n",
+                pc->session_id,
+                pc->fulfillment_url);
+    GNUNET_CRYPTO_hash (pc->session_id,
+                        strlen (pc->session_id),
+                        &session_eh.h_session_id);
+    GNUNET_CRYPTO_hash (pc->fulfillment_url,
+                        strlen (pc->fulfillment_url),
+                        &session_eh.h_fulfillment_url);
+    TMH_db->event_notify (TMH_db->cls,
+                          &session_eh.header,
+                          NULL,
+                          0);
+  }
+  pc->phase = PP_SUCCESS_RESPONSE;
+}
+
+
+/**
+ * Function called with information about a coin that was deposited.
+ *
+ * @param cls closure
+ * @param exchange_url exchange where @a coin_pub was deposited
+ * @param coin_pub public key of the coin
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param refund_fee fee the exchange will charge for refunding this coin
+ */
+static void
+check_coin_paid (void *cls,
+                 const char *exchange_url,
+                 const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                 const struct TALER_Amount *amount_with_fee,
+                 const struct TALER_Amount *deposit_fee,
+                 const struct TALER_Amount *refund_fee)
+{
+  struct PayContext *pc = cls;
+
+  for (unsigned int i = 0; i<pc->coins_cnt; i++)
+  {
+    struct DepositConfirmation *dc = &pc->dc[i];
+
+    if (dc->found_in_db)
+      continue; /* processed earlier, skip "expensive" memcmp() */
+    /* Get matching coin from results*/
     if ( (0 != GNUNET_memcmp (coin_pub,
                               &dc->cdd.coin_pub)) ||
          (0 !=
@@ -1707,9 +1876,11 @@ check_payment_sufficient (struct PayContext *pc)
                                    &pc->egs[i]->wire_fee))
     {
       GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             TALER_EC_GENERIC_CURRENCY_MISMATCH,
-                             total_wire_fee.currency);
+      pay_end (pc,
+               TALER_MHD_reply_with_error (pc->connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           TALER_EC_GENERIC_CURRENCY_MISMATCH,
+                                           total_wire_fee.currency));
       return false;
     }
     if (0 >
@@ -1718,9 +1889,12 @@ check_payment_sufficient (struct PayContext *pc)
                           &pc->egs[i]->wire_fee))
     {
       GNUNET_break (0);
-      resume_pay_with_error (pc,
-                             
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
-                             "could not add exchange wire fee to total");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (
+                 pc->connection,
+                 MHD_HTTP_INTERNAL_SERVER_ERROR,
+                 
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
+                 "could not add exchange wire fee to total"));
       return false;
     }
   }
@@ -1748,9 +1922,12 @@ check_payment_sufficient (struct PayContext *pc)
                                      &dc->cdd.amount)) )
     {
       GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             TALER_EC_GENERIC_CURRENCY_MISMATCH,
-                             dc->deposit_fee.currency);
+      pay_end (pc,
+               TALER_MHD_reply_with_error (
+                 pc->connection,
+                 MHD_HTTP_BAD_REQUEST,
+                 TALER_EC_GENERIC_CURRENCY_MISMATCH,
+                 dc->deposit_fee.currency));
       return false;
     }
     if ( (0 >
@@ -1764,9 +1941,12 @@ check_payment_sufficient (struct PayContext *pc)
     {
       GNUNET_break (0);
       /* Overflow in these amounts? Very strange. */
-      resume_pay_with_error (pc,
-                             
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
-                             "Overflow adding up amounts");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (
+                 pc->connection,
+                 MHD_HTTP_INTERNAL_SERVER_ERROR,
+                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
+                 "Overflow adding up amounts"));
       return false;
     }
     if (1 ==
@@ -1774,13 +1954,15 @@ check_payment_sufficient (struct PayContext *pc)
                           &dc->cdd.amount))
     {
       GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT,
-                             "Deposit fees exceed coin's contribution");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (
+                 pc->connection,
+                 MHD_HTTP_BAD_REQUEST,
+                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT,
+                 "Deposit fees exceed coin's contribution"));
       return false;
     }
-
-  } /* deposit loop */
+  } /* end deposit loop */
 
   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
               "Amount received from wallet: %s\n",
@@ -1805,9 +1987,12 @@ check_payment_sufficient (struct PayContext *pc)
                                  &acc_fee))
   {
     GNUNET_break (0);
-    resume_pay_with_error (pc,
-                           TALER_EC_GENERIC_CURRENCY_MISMATCH,
-                           total_wire_fee.currency);
+    pay_end (pc,
+             TALER_MHD_reply_with_error (
+               pc->connection,
+               MHD_HTTP_BAD_REQUEST,
+               TALER_EC_GENERIC_CURRENCY_MISMATCH,
+               total_wire_fee.currency));
     return false;
   }
 
@@ -1818,9 +2003,12 @@ check_payment_sufficient (struct PayContext *pc)
                         &total_wire_fee))
   {
     GNUNET_break (0);
-    resume_pay_with_error (pc,
-                           
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
-                           "Overflow adding up amounts");
+    pay_end (pc,
+             TALER_MHD_reply_with_error (
+               pc->connection,
+               MHD_HTTP_INTERNAL_SERVER_ERROR,
+               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
+               "Overflow adding up amounts"));
     return false;
   }
   if (-1 == TALER_amount_cmp (&pc->max_fee,
@@ -1845,9 +2033,12 @@ check_payment_sufficient (struct PayContext *pc)
                           &pc->amount))
     {
       GNUNET_break (0);
-      resume_pay_with_error (pc,
-                             
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
-                             "Overflow adding up amounts");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (
+                 pc->connection,
+                 MHD_HTTP_INTERNAL_SERVER_ERROR,
+                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
+                 "Overflow adding up amounts"));
       return false;
     }
   }
@@ -1868,9 +2059,12 @@ check_payment_sufficient (struct PayContext *pc)
                              &pc->total_refunded))
   {
     GNUNET_break (0);
-    resume_pay_with_error (pc,
-                           
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS,
-                           "refunded amount exceeds total payments");
+    pay_end (pc,
+             TALER_MHD_reply_with_error (
+               pc->connection,
+               MHD_HTTP_INTERNAL_SERVER_ERROR,
+               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS,
+               "refunded amount exceeds total payments"));
     return false;
   }
 
@@ -1882,25 +2076,33 @@ check_payment_sufficient (struct PayContext *pc)
                                &total_needed))
     {
       GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDED,
-                             "contract not paid up due to refunds");
-    }
-    else if (-1 < TALER_amount_cmp (&acc_amount,
-                                    &pc->amount))
-    {
-      GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES,
-                             "contract not paid up due to fees (client may 
have calculated them badly)");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (
+                 pc->connection,
+                 MHD_HTTP_PAYMENT_REQUIRED,
+                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDED,
+                 "contract not paid up due to refunds"));
+      return false;
     }
-    else
+    if (-1 < TALER_amount_cmp (&acc_amount,
+                               &pc->amount))
     {
       GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT,
-                             "payment insufficient");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (
+                 pc->connection,
+                 MHD_HTTP_NOT_ACCEPTABLE,
+                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES,
+                 "contract not paid up due to fees (client may have calculated 
them badly)"));
+      return false;
     }
+    GNUNET_break_op (0);
+    pay_end (pc,
+             TALER_MHD_reply_with_error (
+               pc->connection,
+               MHD_HTTP_NOT_ACCEPTABLE,
+               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT,
+               "payment insufficient"));
     return false;
   }
   return true;
@@ -1908,97 +2110,14 @@ check_payment_sufficient (struct PayContext *pc)
 
 
 /**
- * Use database to notify other clients about the
- * payment being completed.
- *
- * @param pc context to trigger notification for
- */
-static void
-trigger_payment_notification (struct PayContext *pc)
-{
-  {
-    struct TMH_OrderPayEventP pay_eh = {
-      .header.size = htons (sizeof (pay_eh)),
-      .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
-      .merchant_pub = pc->hc->instance->merchant_pub
-    };
-
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Notifying clients about payment of order %s\n",
-                pc->order_id);
-    GNUNET_CRYPTO_hash (pc->order_id,
-                        strlen (pc->order_id),
-                        &pay_eh.h_order_id);
-    TMH_db->event_notify (TMH_db->cls,
-                          &pay_eh.header,
-                          NULL,
-                          0);
-  }
-  if ( (NULL != pc->session_id) &&
-       (NULL != pc->fulfillment_url) )
-  {
-    struct TMH_SessionEventP session_eh = {
-      .header.size = htons (sizeof (session_eh)),
-      .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
-      .merchant_pub = pc->hc->instance->merchant_pub
-    };
-
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Notifying clients about session change to %s for %s\n",
-                pc->session_id,
-                pc->fulfillment_url);
-    GNUNET_CRYPTO_hash (pc->session_id,
-                        strlen (pc->session_id),
-                        &session_eh.h_session_id);
-    GNUNET_CRYPTO_hash (pc->fulfillment_url,
-                        strlen (pc->fulfillment_url),
-                        &session_eh.h_fulfillment_url);
-    TMH_db->event_notify (TMH_db->cls,
-                          &session_eh.header,
-                          NULL,
-                          0);
-  }
-}
-
-
-/**
- * Generate response (payment successful)
+ * Execute the DB transaction.  If required (from
+ * soft/serialization errors), the transaction can be
+ * restarted here.
  *
- * @param[in,out] pc payment context where the payment was successful
+ * @param[in,out] pc payment context to transact
  */
 static void
-generate_success_response (struct PayContext *pc)
-{
-  struct GNUNET_CRYPTO_EddsaSignature sig;
-  char *pos_confirmation;
-
-  /* Sign on our end (as the payment did go through, even if it may
-     have been refunded already) */
-  TALER_merchant_pay_sign (&pc->h_contract_terms,
-                           &pc->hc->instance->merchant_priv,
-                           &sig);
-  /* Build the response */
-  pos_confirmation = (NULL == pc->pos_key)
-    ? NULL
-    : TALER_build_pos_confirmation (pc->pos_key,
-                                    pc->pos_alg,
-                                    &pc->amount,
-                                    pc->timestamp);
-  resume_pay_with_response (
-    pc,
-    MHD_HTTP_OK,
-    TALER_MHD_MAKE_JSON_PACK (
-      GNUNET_JSON_pack_allow_null (
-        GNUNET_JSON_pack_string ("pos_confirmation",
-                                 pos_confirmation)),
-      GNUNET_JSON_pack_data_auto ("sig",
-                                  &sig)));
-  GNUNET_free (pos_confirmation);
-}
-
-
-static void
-execute_pay_transaction (struct PayContext *pc)
+phase_execute_pay_transaction (struct PayContext *pc)
 {
   struct TMH_HandlerContext *hc = pc->hc;
   const char *instance_id = hc->instance->settings.id;
@@ -2007,12 +2126,13 @@ execute_pay_transaction (struct PayContext *pc)
   if (pc->retry_counter++ > MAX_RETRIES)
   {
     GNUNET_break (0);
-    resume_pay_with_error (pc,
-                           TALER_EC_GENERIC_DB_SOFT_FAILURE,
-                           NULL);
+    pay_end (pc,
+             TALER_MHD_reply_with_error (pc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
+                                         NULL));
     return;
   }
-  GNUNET_assert (GNUNET_YES == pc->suspended);
 
   /* Initialize some amount accumulators
      (used in check_coin_paid(), check_coin_refunded()
@@ -2037,9 +2157,11 @@ execute_pay_transaction (struct PayContext *pc)
                      "run pay"))
   {
     GNUNET_break (0);
-    resume_pay_with_error (pc,
-                           TALER_EC_GENERIC_DB_START_FAILED,
-                           NULL);
+    pay_end (pc,
+             TALER_MHD_reply_with_error (pc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_START_FAILED,
+                                         NULL));
     return;
   }
 
@@ -2056,28 +2178,28 @@ execute_pay_transaction (struct PayContext *pc)
     {
       TMH_db->rollback (TMH_db->cls);
       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        execute_pay_transaction (pc);
-        return;
-      }
+        return; /* do it again */
       /* Always report on hard error as well to enable diagnostics */
       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      resume_pay_with_error (pc,
-                             TALER_EC_GENERIC_DB_FETCH_FAILED,
-                             "lookup deposits");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (pc->connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "lookup deposits"));
       return;
     }
     if (pc->deposit_currency_mismatch)
     {
       GNUNET_break_op (0);
-      resume_pay_with_error (pc,
-                             TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
-                             pc->amount.currency);
+      pay_end (pc,
+               TALER_MHD_reply_with_error (pc->connection,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           
TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+                                           pc->amount.currency));
       return;
     }
   }
 
-
   {
     enum GNUNET_DB_QueryStatus qs;
 
@@ -2091,23 +2213,24 @@ execute_pay_transaction (struct PayContext *pc)
     {
       TMH_db->rollback (TMH_db->cls);
       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        execute_pay_transaction (pc);
-        return;
-      }
+        return; /* do it again */
       /* Always report on hard error as well to enable diagnostics */
       GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      resume_pay_with_error (pc,
-                             TALER_EC_GENERIC_DB_FETCH_FAILED,
-                             "lookup refunds");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (pc->connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "lookup refunds"));
       return;
     }
     if (pc->refund_currency_mismatch)
     {
       TMH_db->rollback (TMH_db->cls);
-      resume_pay_with_error (pc,
-                             TALER_EC_GENERIC_DB_FETCH_FAILED,
-                             "refund currency in database does not match order 
currency");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (pc->connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "refund currency in database does 
not match order currency"));
       return;
     }
   }
@@ -2117,11 +2240,10 @@ execute_pay_transaction (struct PayContext *pc)
   {
     /* we made no DB changes, so we can just rollback */
     TMH_db->rollback (TMH_db->cls);
-
     /* Ok, we need to first go to the network to process more coins.
        We that interaction in *tiny* transactions (hence the rollback
        above). */
-    start_batch_deposits (pc);
+    pc->phase = PP_BATCH_DEPOSITS;
     return;
   }
 
@@ -2149,14 +2271,13 @@ execute_pay_transaction (struct PayContext *pc)
     {
       TMH_db->rollback (TMH_db->cls);
       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        execute_pay_transaction (pc);
-        return;
-      }
+        return; /* do it again */
       GNUNET_break (0);
-      resume_pay_with_error (pc,
-                             TALER_EC_GENERIC_DB_STORE_FAILED,
-                             "mark contract paid");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (pc->connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_STORE_FAILED,
+                                           "mark contract paid"));
       return;
     }
   }
@@ -2184,14 +2305,13 @@ execute_pay_transaction (struct PayContext *pc)
     {
       TMH_db->rollback (TMH_db->cls);
       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        execute_pay_transaction (pc);
-        return;
-      }
+        return; /* do it again */
       GNUNET_break (0);
-      resume_pay_with_error (pc,
-                             TALER_EC_GENERIC_DB_STORE_FAILED,
-                             "failed to trigger webhooks");
+      pay_end (pc,
+               TALER_MHD_reply_with_error (pc->connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_STORE_FAILED,
+                                           "failed to trigger webhooks"));
       return;
     }
   }
@@ -2205,250 +2325,66 @@ execute_pay_transaction (struct PayContext *pc)
       /* commit failed */
       TMH_db->rollback (TMH_db->cls);
       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
-      {
-        execute_pay_transaction (pc);
-        return;
-      }
+        return; /* do it again */
       GNUNET_break (0);
-      resume_pay_with_error (pc,
-                             TALER_EC_GENERIC_DB_COMMIT_FAILED,
-                             NULL);
+      pay_end (pc,
+               TALER_MHD_reply_with_error (pc->connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_COMMIT_FAILED,
+                                           NULL));
       return;
     }
-    trigger_payment_notification (pc);
   }
-  generate_success_response (pc);
+  pc->phase = PP_PAYMENT_NOTIFICATION;
 }
 
 
 /**
- * Try to parse the pay request into the given pay context.
- * Schedules an error response in the connection on failure.
+ * Function called with information about a coin that was deposited.
+ * Checks if this coin is in our list of deposits as well.
  *
- * @param[in,out] pc context we use to handle the payment
- * @return #GNUNET_OK on success,
- *         #GNUNET_NO on failure (response was queued with MHD)
- *         #GNUNET_SYSERR on hard error (MHD connection must be dropped)
+ * @param cls closure with our `struct PayContext *`
+ * @param deposit_serial which deposit operation is this about
+ * @param exchange_url URL of the exchange that issued the coin
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param h_wire hash of merchant's wire details
+ * @param coin_pub public key of the coin
  */
-static enum GNUNET_GenericReturnValue
-parse_pay (struct PayContext *pc)
+static void
+deposit_paid_check (
+  void *cls,
+  uint64_t deposit_serial,
+  const char *exchange_url,
+  const struct TALER_MerchantWireHashP *h_wire,
+  const struct TALER_Amount *amount_with_fee,
+  const struct TALER_Amount *deposit_fee,
+  const struct TALER_CoinSpendPublicKeyP *coin_pub)
 {
-  const char *session_id = NULL;
-  const json_t *coins;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_array_const ("coins",
-                                  &coins),
-    GNUNET_JSON_spec_mark_optional (
-      GNUNET_JSON_spec_string ("session_id",
-                               &session_id),
-      NULL),
-    GNUNET_JSON_spec_end ()
-  };
+  struct PayContext *pc = cls;
 
+  for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
-    enum GNUNET_GenericReturnValue res;
+    struct DepositConfirmation *dci = &pc->dc[i];
 
-    res = TALER_MHD_parse_json_data (pc->connection,
-                                     pc->hc->request_body,
-                                     spec);
-    if (GNUNET_YES != res)
+    if ( (0 ==
+          GNUNET_memcmp (&dci->cdd.coin_pub,
+                         coin_pub)) &&
+         (0 ==
+          strcmp (dci->exchange_url,
+                  exchange_url)) &&
+         (GNUNET_YES ==
+          TALER_amount_cmp_currency (&dci->cdd.amount,
+                                     amount_with_fee)) &&
+         (0 ==
+          TALER_amount_cmp (&dci->cdd.amount,
+                            amount_with_fee)) )
     {
-      GNUNET_break_op (0);
-      return res;
+      dci->matched_in_db = true;
+      break;
     }
   }
-
-  /* copy session ID (if set) */
-  if (NULL != session_id)
-  {
-    pc->session_id = GNUNET_strdup (session_id);
-  }
-  else
-  {
-    /* use empty string as default if client didn't specify it */
-    pc->session_id = GNUNET_strdup ("");
-  }
-  pc->coins_cnt = json_array_size (coins);
-  if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS)
-  {
-    GNUNET_break_op (0);
-    return (MHD_YES ==
-            TALER_MHD_reply_with_error (
-              pc->connection,
-              MHD_HTTP_BAD_REQUEST,
-              TALER_EC_GENERIC_PARAMETER_MALFORMED,
-              "'coins' array too long"))
-        ? GNUNET_NO
-        : GNUNET_SYSERR;
-  }
-
-  /* note: 1 coin = 1 deposit confirmation expected */
-  pc->dc = GNUNET_new_array (pc->coins_cnt,
-                             struct DepositConfirmation);
-
-  /* This loop populates the array 'dc' in 'pc' */
-  {
-    unsigned int coins_index;
-    json_t *coin;
-
-    json_array_foreach (coins, coins_index, coin)
-    {
-      struct DepositConfirmation *dc = &pc->dc[coins_index];
-      const char *exchange_url;
-      struct GNUNET_JSON_Specification ispec[] = {
-        GNUNET_JSON_spec_fixed_auto ("coin_sig",
-                                     &dc->cdd.coin_sig),
-        GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                     &dc->cdd.coin_pub),
-        TALER_JSON_spec_denom_sig ("ub_sig",
-                                   &dc->cdd.denom_sig),
-        GNUNET_JSON_spec_fixed_auto ("h_denom",
-                                     &dc->cdd.h_denom_pub),
-        TALER_JSON_spec_amount_any ("contribution",
-                                    &dc->cdd.amount),
-        TALER_JSON_spec_web_url ("exchange_url",
-                                 &exchange_url),
-        /* if a minimum age was required, the minimum_age_sig and
-         * age_commitment must be provided */
-        GNUNET_JSON_spec_mark_optional (
-          GNUNET_JSON_spec_fixed_auto ("minimum_age_sig",
-                                       &dc->minimum_age_sig),
-          &dc->no_minimum_age_sig),
-        GNUNET_JSON_spec_mark_optional (
-          TALER_JSON_spec_age_commitment ("age_commitment",
-                                          &dc->age_commitment),
-          &dc->no_age_commitment),
-        /* if minimum age was not required, but coin with age restriction set
-         * was used, h_age_commitment must be provided. */
-        GNUNET_JSON_spec_mark_optional (
-          GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
-                                       &dc->cdd.h_age_commitment),
-          &dc->no_h_age_commitment),
-        GNUNET_JSON_spec_end ()
-      };
-      enum GNUNET_GenericReturnValue res;
-      bool have_eg = false;
-
-      res = TALER_MHD_parse_json_data (pc->connection,
-                                       coin,
-                                       ispec);
-      if (GNUNET_YES != res)
-      {
-        GNUNET_break_op (0);
-        return res;
-      }
-      for (unsigned int j = 0; j<coins_index; j++)
-      {
-        if (0 ==
-            GNUNET_memcmp (&dc->cdd.coin_pub,
-                           &pc->dc[j].cdd.coin_pub))
-        {
-          GNUNET_break_op (0);
-          return (MHD_YES ==
-                  TALER_MHD_reply_with_error (pc->connection,
-                                              MHD_HTTP_BAD_REQUEST,
-                                              
TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                                              "duplicate coin in list"))
-                ? GNUNET_NO
-                : GNUNET_SYSERR;
-        }
-      }
-
-      dc->exchange_url = GNUNET_strdup (exchange_url);
-      dc->index = coins_index;
-      dc->pc = pc;
-
-      /* Check the consistency of the (potential) age restriction
-       * information. */
-      if (dc->no_age_commitment != dc->no_minimum_age_sig)
-      {
-        GNUNET_break_op (0);
-        return (MHD_YES ==
-                TALER_MHD_reply_with_error (
-                  pc->connection,
-                  MHD_HTTP_BAD_REQUEST,
-                  TALER_EC_GENERIC_PARAMETER_MALFORMED,
-                  "inconsistent: 'age_commitment' vs. 'minimum_age_sig'"
-                  )
-                )
-              ? GNUNET_NO
-              : GNUNET_SYSERR;
-      }
-
-      /* Setup exchange group */
-      for (unsigned int i = 0; i<pc->num_exchanges; i++)
-      {
-        if (0 ==
-            strcmp (pc->egs[i]->exchange_url,
-                    exchange_url))
-        {
-          have_eg = true;
-          break;
-        }
-      }
-      if (! have_eg)
-      {
-        struct ExchangeGroup *eg;
-
-        eg = GNUNET_new (struct ExchangeGroup);
-        eg->pc = pc;
-        eg->exchange_url = dc->exchange_url;
-        GNUNET_array_append (pc->egs,
-                             pc->num_exchanges,
-                             eg);
-      }
-    }
-  }
-  return GNUNET_OK;
-}
-
-
-/**
- * Function called with information about a coin that was deposited.
- * Checks if this coin is in our list of deposits as well.
- *
- * @param cls closure with our `struct PayContext *`
- * @param deposit_serial which deposit operation is this about
- * @param exchange_url URL of the exchange that issued the coin
- * @param amount_with_fee amount the exchange will deposit for this coin
- * @param deposit_fee fee the exchange will charge for this coin
- * @param h_wire hash of merchant's wire details
- * @param coin_pub public key of the coin
- */
-static void
-deposit_paid_check (
-  void *cls,
-  uint64_t deposit_serial,
-  const char *exchange_url,
-  const struct TALER_MerchantWireHashP *h_wire,
-  const struct TALER_Amount *amount_with_fee,
-  const struct TALER_Amount *deposit_fee,
-  const struct TALER_CoinSpendPublicKeyP *coin_pub)
-{
-  struct PayContext *pc = cls;
-
-  for (unsigned int i = 0; i<pc->coins_cnt; i++)
-  {
-    struct DepositConfirmation *dci = &pc->dc[i];
-
-    if ( (0 ==
-          GNUNET_memcmp (&dci->cdd.coin_pub,
-                         coin_pub)) &&
-         (0 ==
-          strcmp (dci->exchange_url,
-                  exchange_url)) &&
-         (GNUNET_YES ==
-          TALER_amount_cmp_currency (&dci->cdd.amount,
-                                     amount_with_fee)) &&
-         (0 ==
-          TALER_amount_cmp (&dci->cdd.amount,
-                            amount_with_fee)) )
-    {
-      dci->matched_in_db = true;
-      break;
-    }
-  }
-}
+}
 
 
 /**
@@ -2456,11 +2392,9 @@ deposit_paid_check (
  * the payment is idempotent, or refunds the excess payment.
  *
  * @param[in,out] pc context we use to handle the payment
- * @return #GNUNET_NO if response was queued with MHD
- *         #GNUNET_SYSERR on hard error (MHD connection must be dropped)
  */
-static enum GNUNET_GenericReturnValue
-handle_contract_paid (struct PayContext *pc)
+static void
+phase_contract_paid (struct PayContext *pc)
 {
   enum GNUNET_DB_QueryStatus qs;
   bool unmatched = false;
@@ -2473,13 +2407,12 @@ handle_contract_paid (struct PayContext *pc)
   if (qs <= 0)
   {
     GNUNET_break (0);
-    return (MHD_YES ==
-            TALER_MHD_reply_with_error (pc->connection,
-                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                        "lookup_deposits_by_order"))
-       ? GNUNET_NO
-       : GNUNET_SYSERR;
+    pay_end (pc,
+             TALER_MHD_reply_with_error (pc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         "lookup_deposits_by_order"));
+    return;
   }
   for (unsigned int i = 0; i<pc->coins_cnt; i++)
   {
@@ -2499,14 +2432,13 @@ handle_contract_paid (struct PayContext *pc)
     TALER_merchant_pay_sign (&pc->h_contract_terms,
                              &pc->hc->instance->merchant_priv,
                              &sig);
-    return (MHD_YES ==
-            TALER_MHD_REPLY_JSON_PACK (
-              pc->connection,
-              MHD_HTTP_OK,
-              GNUNET_JSON_pack_data_auto ("sig",
-                                          &sig)))
-       ? GNUNET_NO
-       : GNUNET_SYSERR;
+    pay_end (pc,
+             TALER_MHD_REPLY_JSON_PACK (
+               pc->connection,
+               MHD_HTTP_OK,
+               GNUNET_JSON_pack_data_auto ("sig",
+                                           &sig)));
+    return;
   }
   /* Conflict, double-payment detected! */
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -2543,29 +2475,25 @@ handle_contract_paid (struct PayContext *pc)
           GNUNET_JSON_pack_uint64 ("rtransaction_id",
                                    0))));
   }
-  return (MHD_YES ==
-          TALER_MHD_REPLY_JSON_PACK (
-            pc->connection,
-            MHD_HTTP_CONFLICT,
-            TALER_MHD_PACK_EC (
-              TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID),
-            GNUNET_JSON_pack_array_steal ("refunds",
-                                          refunds)))
-       ? GNUNET_NO
-       : GNUNET_SYSERR;
+  pay_end (pc,
+           TALER_MHD_REPLY_JSON_PACK (
+             pc->connection,
+             MHD_HTTP_CONFLICT,
+             TALER_MHD_PACK_EC (
+                                
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID),
+             GNUNET_JSON_pack_array_steal ("refunds",
+                                           refunds)));
 }
 
 
 /**
- * Check the database state for the given order. * Schedules an error response 
in the connection on failure.
+ * Check the database state for the given order.
+ * Schedules an error response in the connection on failure.
  *
- * @param pc context we use to handle the payment
- * @return #GNUNET_OK on success,
- *         #GNUNET_NO on failure (response was queued with MHD)
- *         #GNUNET_SYSERR on hard error (MHD connection must be dropped)
+ * @param[in,out] pc context we use to handle the payment
  */
-static enum GNUNET_GenericReturnValue
-check_contract (struct PayContext *pc)
+static void
+phase_check_contract (struct PayContext *pc)
 {
   /* obtain contract terms */
   enum GNUNET_DB_QueryStatus qs;
@@ -2592,23 +2520,21 @@ check_contract (struct PayContext *pc)
     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
     /* Always report on hard error to enable diagnostics */
     GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-    return (MHD_YES ==
-            TALER_MHD_reply_with_error (pc->connection,
-                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                        "contract terms"))
-       ? GNUNET_NO
-       : GNUNET_SYSERR;
+    pay_end (pc,
+             TALER_MHD_reply_with_error (pc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         "contract terms"));
+    return;
   }
   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
   {
-    return (MHD_YES ==
-            TALER_MHD_reply_with_error (pc->connection,
-                                        MHD_HTTP_NOT_FOUND,
-                                        
TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
-                                        pc->order_id))
-       ? GNUNET_NO
-       : GNUNET_SYSERR;
+    pay_end (pc,
+             TALER_MHD_reply_with_error (pc->connection,
+                                         MHD_HTTP_NOT_FOUND,
+                                         
TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+                                         pc->order_id));
+    return;
   }
   /* hash contract (needed later) */
   if (GNUNET_OK !=
@@ -2616,20 +2542,20 @@ check_contract (struct PayContext *pc)
                                 &pc->h_contract_terms))
   {
     GNUNET_break (0);
-    return (MHD_YES ==
-            TALER_MHD_reply_with_error (pc->connection,
-                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                        
TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
-                                        NULL))
-       ? GNUNET_NO
-       : GNUNET_SYSERR;
+    pay_end (pc,
+             TALER_MHD_reply_with_error (pc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+                                         NULL));
+    return;
   }
   if (paid)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Order `%s' paid, checking for double-payment\n",
                 pc->order_id);
-    return handle_contract_paid (pc);
+    pc->phase = PP_CONTRACT_PAID;
+    return;
   }
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Handling payment for order `%s' with contract hash `%s'\n",
@@ -2642,13 +2568,12 @@ check_contract (struct PayContext *pc)
   {
     /* invalid contract */
     GNUNET_break (0);
-    return (MHD_YES ==
-            TALER_MHD_reply_with_error (pc->connection,
-                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                        
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING,
-                                        NULL))
-       ? GNUNET_NO
-       : GNUNET_SYSERR;
+    pay_end (pc,
+             TALER_MHD_reply_with_error (pc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_MERCHANT_FIELD_MISSING,
+                                         NULL));
+    return;
   }
 
   /* Get details from contract and check fundamentals */
@@ -2691,7 +2616,11 @@ check_contract (struct PayContext *pc)
     if (GNUNET_YES != res)
     {
       GNUNET_break (0);
-      return res;
+      pay_end (pc,
+               (GNUNET_NO == res)
+               ? MHD_YES
+               : MHD_NO);
+      return;
     }
   }
 
@@ -2700,10 +2629,12 @@ check_contract (struct PayContext *pc)
                                  &pc->amount))
   {
     GNUNET_break (0);
-    return TALER_MHD_reply_with_error (pc->connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                       "'max_fee' in database does not match 
currency of contract price");
+    pay_end (pc,
+             TALER_MHD_reply_with_error (pc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                         "'max_fee' in database does not match 
currency of contract price"));
+    return;
   }
 
   for (unsigned int i=0;i<pc->coins_cnt;i++)
@@ -2715,13 +2646,12 @@ check_contract (struct PayContext *pc)
                                    &pc->amount))
     {
       GNUNET_break_op (0);
-      return (MHD_YES ==
-              TALER_MHD_reply_with_error (pc->connection,
-                                          MHD_HTTP_CONFLICT,
-                                          
TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
-                                          pc->amount.currency))
-        ? GNUNET_NO
-        : GNUNET_SYSERR;
+      pay_end (pc,
+               TALER_MHD_reply_with_error (pc->connection,
+                                           MHD_HTTP_CONFLICT,
+                                           
TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+                                           pc->amount.currency));
+      return;
     }
   }
 
@@ -2731,21 +2661,22 @@ check_contract (struct PayContext *pc)
   {
     /* This should already have been checked when creating the order! */
     GNUNET_break (0);
-    return TALER_MHD_reply_with_error (pc->connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
-                                       NULL);
+    pay_end (pc,
+             TALER_MHD_reply_with_error (pc->connection,
+                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                         
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
+                                         NULL));
+    return;
   }
   if (GNUNET_TIME_absolute_is_past (pc->pay_deadline.abs_time))
   {
     /* too late */
-    return (MHD_YES ==
-            TALER_MHD_reply_with_error (pc->connection,
-                                        MHD_HTTP_GONE,
-                                        
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
-                                        NULL))
-       ? GNUNET_NO
-       : GNUNET_SYSERR;
+    pay_end (pc,
+             TALER_MHD_reply_with_error (pc->connection,
+                                         MHD_HTTP_GONE,
+                                         
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
+                                         NULL));
+    return;
   }
 
   /* Make sure wire method (still) exists for this instance */
@@ -2759,35 +2690,251 @@ check_contract (struct PayContext *pc)
     if (NULL == wm)
     {
       GNUNET_break (0);
-      return TALER_MHD_reply_with_error (pc->connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN,
-                                         NULL);
+      pay_end (pc,
+               TALER_MHD_reply_with_error (pc->connection,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           
TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN,
+                                           NULL));
+      return;
     }
     pc->wm = wm;
   }
+  pc->phase = PP_PAY_TRANSACTION;
+}
+
+
+/**
+ * Try to parse the pay request into the given pay context.
+ * Schedules an error response in the connection on failure.
+ *
+ * @param[in,out] pc context we use to handle the payment
+ */
+static void
+phase_parse_pay (struct PayContext *pc)
+{
+  const char *session_id = NULL;
+  const json_t *coins;
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_array_const ("coins",
+                                  &coins),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_string ("session_id",
+                               &session_id),
+      NULL),
+    GNUNET_JSON_spec_end ()
+  };
+
+  GNUNET_assert (PP_INIT == pc->phase);
+  {
+    enum GNUNET_GenericReturnValue res;
 
-  return GNUNET_OK;
+    res = TALER_MHD_parse_json_data (pc->connection,
+                                     pc->hc->request_body,
+                                     spec);
+    if (GNUNET_YES != res)
+    {
+      GNUNET_break_op (0);
+      pay_end (pc,
+               (GNUNET_NO == res)
+               ? MHD_YES
+               : MHD_NO);
+      return;
+    }
+  }
+
+  /* copy session ID (if set) */
+  if (NULL != session_id)
+  {
+    pc->session_id = GNUNET_strdup (session_id);
+  }
+  else
+  {
+    /* use empty string as default if client didn't specify it */
+    pc->session_id = GNUNET_strdup ("");
+  }
+  pc->coins_cnt = json_array_size (coins);
+  if (pc->coins_cnt > MAX_COIN_ALLOWED_COINS)
+  {
+    GNUNET_break_op (0);
+    pay_end (pc,
+             TALER_MHD_reply_with_error (
+               pc->connection,
+               MHD_HTTP_BAD_REQUEST,
+               TALER_EC_GENERIC_PARAMETER_MALFORMED,
+               "'coins' array too long"));
+    return;
+  }
+
+  /* note: 1 coin = 1 deposit confirmation expected */
+  pc->dc = GNUNET_new_array (pc->coins_cnt,
+                             struct DepositConfirmation);
+
+  /* This loop populates the array 'dc' in 'pc' */
+  {
+    unsigned int coins_index;
+    json_t *coin;
+
+    json_array_foreach (coins, coins_index, coin)
+    {
+      struct DepositConfirmation *dc = &pc->dc[coins_index];
+      const char *exchange_url;
+      struct GNUNET_JSON_Specification ispec[] = {
+        GNUNET_JSON_spec_fixed_auto ("coin_sig",
+                                     &dc->cdd.coin_sig),
+        GNUNET_JSON_spec_fixed_auto ("coin_pub",
+                                     &dc->cdd.coin_pub),
+        TALER_JSON_spec_denom_sig ("ub_sig",
+                                   &dc->cdd.denom_sig),
+        GNUNET_JSON_spec_fixed_auto ("h_denom",
+                                     &dc->cdd.h_denom_pub),
+        TALER_JSON_spec_amount_any ("contribution",
+                                    &dc->cdd.amount),
+        TALER_JSON_spec_web_url ("exchange_url",
+                                 &exchange_url),
+        /* if a minimum age was required, the minimum_age_sig and
+         * age_commitment must be provided */
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_fixed_auto ("minimum_age_sig",
+                                       &dc->minimum_age_sig),
+          &dc->no_minimum_age_sig),
+        GNUNET_JSON_spec_mark_optional (
+          TALER_JSON_spec_age_commitment ("age_commitment",
+                                          &dc->age_commitment),
+          &dc->no_age_commitment),
+        /* if minimum age was not required, but coin with age restriction set
+         * was used, h_age_commitment must be provided. */
+        GNUNET_JSON_spec_mark_optional (
+          GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+                                       &dc->cdd.h_age_commitment),
+          &dc->no_h_age_commitment),
+        GNUNET_JSON_spec_end ()
+      };
+      enum GNUNET_GenericReturnValue res;
+      bool have_eg = false;
+
+      res = TALER_MHD_parse_json_data (pc->connection,
+                                       coin,
+                                       ispec);
+      if (GNUNET_YES != res)
+      {
+        GNUNET_break_op (0);
+        pay_end (pc,
+                 (GNUNET_NO == res)
+                 ? MHD_YES
+                 : MHD_NO);
+      }
+      for (unsigned int j = 0; j<coins_index; j++)
+      {
+        if (0 ==
+            GNUNET_memcmp (&dc->cdd.coin_pub,
+                           &pc->dc[j].cdd.coin_pub))
+        {
+          GNUNET_break_op (0);
+          pay_end (pc,
+                   TALER_MHD_reply_with_error (pc->connection,
+                                               MHD_HTTP_BAD_REQUEST,
+                                               
TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                                               "duplicate coin in list"));
+          return;
+        }
+      }
+
+      dc->exchange_url = GNUNET_strdup (exchange_url);
+      dc->index = coins_index;
+      dc->pc = pc;
+
+      /* Check the consistency of the (potential) age restriction
+       * information. */
+      if (dc->no_age_commitment != dc->no_minimum_age_sig)
+      {
+        GNUNET_break_op (0);
+        pay_end (pc,
+                 TALER_MHD_reply_with_error (
+                   pc->connection,
+                   MHD_HTTP_BAD_REQUEST,
+                   TALER_EC_GENERIC_PARAMETER_MALFORMED,
+                   "inconsistent: 'age_commitment' vs. 'minimum_age_sig'"
+                                             ));
+        return;
+      }
+
+      /* Setup exchange group */
+      for (unsigned int i = 0; i<pc->num_exchanges; i++)
+      {
+        if (0 ==
+            strcmp (pc->egs[i]->exchange_url,
+                    exchange_url))
+        {
+          have_eg = true;
+          break;
+        }
+      }
+      if (! have_eg)
+      {
+        struct ExchangeGroup *eg;
+
+        eg = GNUNET_new (struct ExchangeGroup);
+        eg->pc = pc;
+        eg->exchange_url = dc->exchange_url;
+        GNUNET_array_append (pc->egs,
+                             pc->num_exchanges,
+                             eg);
+      }
+    }
+  }
+  pc->phase = PP_CHECK_CONTRACT;
 }
 
 
 /**
- * Handle a timeout for the processing of the pay request.
+ * Custom cleanup routine for a `struct PayContext`.
  *
- * @param cls our `struct PayContext`
+ * @param cls the `struct PayContext` to clean up.
  */
 static void
-handle_pay_timeout (void *cls)
+pay_context_cleanup (void *cls)
 {
   struct PayContext *pc = cls;
 
-  pc->timeout_task = NULL;
-  GNUNET_assert (GNUNET_YES == pc->suspended);
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Resuming pay with error after timeout\n");
-  resume_pay_with_error (pc,
-                         TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
-                         NULL);
+  if (NULL != pc->timeout_task)
+  {
+    GNUNET_SCHEDULER_cancel (pc->timeout_task);
+    pc->timeout_task = NULL;
+  }
+  if (NULL != pc->contract_terms)
+  {
+    json_decref (pc->contract_terms);
+    pc->contract_terms = NULL;
+  }
+  for (unsigned int i = 0; i<pc->coins_cnt; i++)
+  {
+    struct DepositConfirmation *dc = &pc->dc[i];
+
+    TALER_denom_sig_free (&dc->cdd.denom_sig);
+    GNUNET_free (dc->exchange_url);
+  }
+  GNUNET_free (pc->dc);
+  for (unsigned int i = 0; i<pc->num_exchanges; i++)
+  {
+    struct ExchangeGroup *eg = pc->egs[i];
+
+    if (NULL != eg->fo)
+      TMH_EXCHANGES_keys4exchange_cancel (eg->fo);
+    GNUNET_free (eg);
+  }
+  GNUNET_free (pc->egs);
+  if (NULL != pc->response)
+  {
+    MHD_destroy_response (pc->response);
+    pc->response = NULL;
+  }
+  GNUNET_free (pc->fulfillment_url);
+  GNUNET_free (pc->session_id);
+  GNUNET_CONTAINER_DLL_remove (pc_head,
+                               pc_tail,
+                               pc);
+  GNUNET_free (pc->pos_key);
+  GNUNET_free (pc);
 }
 
 
@@ -2797,59 +2944,76 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler 
*rh,
                         struct TMH_HandlerContext *hc)
 {
   struct PayContext *pc = hc->ctx;
-  enum GNUNET_GenericReturnValue ret;
 
   GNUNET_assert (NULL != hc->infix);
   if (NULL == pc)
   {
     pc = GNUNET_new (struct PayContext);
-    GNUNET_CONTAINER_DLL_insert (pc_head,
-                                 pc_tail,
-                                 pc);
     pc->connection = connection;
     pc->hc = hc;
     pc->order_id = hc->infix;
     hc->ctx = pc;
     hc->cc = &pay_context_cleanup;
-    ret = parse_pay (pc);
-    if (GNUNET_OK != ret)
-      return (GNUNET_NO == ret)
-       ? MHD_YES
-       : MHD_NO;
-  }
-  if (GNUNET_SYSERR == pc->suspended)
-    return MHD_NO; /* during shutdown, we don't generate any more replies */
-  GNUNET_assert (GNUNET_NO == pc->suspended);
-  if (0 != pc->response_code)
-  {
-    /* We are *done* processing the request, just queue the response (!) */
-    if (UINT_MAX == pc->response_code)
+    GNUNET_CONTAINER_DLL_insert (pc_head,
+                                 pc_tail,
+                                 pc);
+  }
+  while (1)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Processing /pay in phase %d\n",
+                (int) pc->phase);
+    switch (pc->phase)
     {
-      GNUNET_break (0);
-      return MHD_NO; /* hard error */
+    case PP_INIT:
+      phase_parse_pay (pc);
+      break;
+    case PP_CHECK_CONTRACT:
+      phase_check_contract (pc);
+      break;
+    case PP_CONTRACT_PAID:
+      phase_contract_paid (pc);
+      break;
+    case PP_PAY_TRANSACTION:
+      phase_execute_pay_transaction (pc);
+      break;
+    case PP_BATCH_DEPOSITS:
+      phase_batch_deposits (pc);
+      break;
+    case PP_PAYMENT_NOTIFICATION:
+      phase_payment_notification (pc);
+      break;
+    case PP_SUCCESS_RESPONSE:
+      phase_success_response (pc);
+      break;
+    case PP_RETURN_RESPONSE:
+      phase_return_response (pc);
+      break;
+    case PP_END_YES:
+      return MHD_YES;
+    case PP_END_NO:
+      return MHD_NO;
+    }
+    switch (pc->suspended)
+    {
+    case GNUNET_SYSERR:
+      /* during shutdown, we don't generate any more replies */
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Processing /pay ends due to shutdown in phase %d\n",
+                  (int) pc->phase);
+      return MHD_NO; 
+    case GNUNET_NO:
+      /* continue to next phase */
+      break;
+    case GNUNET_YES:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Processing /pay suspended in phase %d\n",
+                  (int) pc->phase);
+      return MHD_YES;
     }
-    return MHD_queue_response (connection,
-                               pc->response_code,
-                               pc->response);
   }
-  ret = check_contract (pc);
-  if (GNUNET_OK != ret)
-    return (GNUNET_NO == ret)
-      ? MHD_YES
-      : MHD_NO;
-
-  /* Payment not finished, suspend while we interact with the exchange */
-  MHD_suspend_connection (connection);
-  pc->suspended = GNUNET_YES;
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Suspending pay handling while working with the exchange\n");
-  GNUNET_assert (NULL == pc->timeout_task);
-  pc->timeout_task
-    = GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->coins_cnt),
-                                    &handle_pay_timeout,
-                                    pc);
-  GNUNET_assert (NULL != pc->wm);
-  execute_pay_transaction (pc);
+  /* impossible to get here */
+  GNUNET_assert (0);
   return MHD_YES;
 }
 

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



reply via email to

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