gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant] branch master updated: first steps towards cleaning up


From: gnunet
Subject: [taler-merchant] branch master updated: first steps towards cleaning up GET /private/orders/ request handling logic
Date: Wed, 03 Jan 2024 11:27:43 +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 5284c114 first steps towards cleaning up GET /private/orders/ request 
handling logic
5284c114 is described below

commit 5284c114cfc2a0ba5cebf133228d338befae6d1a
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Wed Jan 3 11:10:18 2024 +0100

    first steps towards cleaning up GET /private/orders/ request handling logic
---
 .../taler-merchant-httpd_private-get-orders-ID.c   | 2273 ++++++++++++--------
 1 file changed, 1348 insertions(+), 925 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_private-get-orders-ID.c 
b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
index 98bf2ab8..cd26d378 100644
--- a/src/backend/taler-merchant-httpd_private-get-orders-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-orders-ID.c
@@ -108,12 +108,108 @@ struct TransferQuery
 };
 
 
+/**
+ * Phases of order processing.
+ */
+enum GetOrderPhase
+{
+  /**
+   * Initialization.
+   */
+  GOP_INIT = 0,
+
+  /**
+   * Obtain contract terms from database.
+   */
+  GOP_FETCH_CONTRACT = 1,
+
+  /**
+   * Parse the contract terms.
+   */
+  GOP_PARSE_CONTRACT = 2,
+
+  /**
+   * Check if the contract was fully paid.
+   */
+  GOP_CHECK_PAID = 3,
+
+  /**
+   * Check if the wallet may have purchased an equivalent
+   * order before and we need to redirect the wallet to
+   * an existing paid order.
+   */
+  GOP_CHECK_REPURCHASE = 4,
+
+  /**
+   * Terminate processing of unpaid orders, either by
+   * suspending until payment or by returning the
+   * unpaid order status.
+   */
+  GOP_UNPAID_FINISH = 5,
+
+  /**
+   * Check if the (paid) order was refunded.
+   */
+  GOP_CHECK_REFUNDS = 6,
+
+  /**
+   * Check if the exchange transferred the funds to
+   * the merchant.
+   */
+  GOP_CHECK_EXCHANGE_TRANSFERS = 7,
+
+  /**
+   * We are suspended awaiting a response from the
+   * exchange.
+   */
+  GOP_SUSPENDED_ON_EXCHANGE = 8,
+
+  /**
+   * Check local records for transfers of funds to
+   * the merchant.
+   */
+  GOP_CHECK_LOCAL_TRANSFERS = 9,
+
+  /**
+   * Generate final comprehensive result.
+   */
+  GOP_REPLY_RESULT = 10,
+
+  /**
+   * End with the HTTP status and error code in
+   * wire_hc and wire_ec.
+   */
+  GOP_ERROR = 11,
+
+  /**
+   * We are suspended awaiting payment.
+   */
+  GOP_SUSPENDED_ON_UNPAID = 12,
+
+  /**
+   * Processing is done, return #MHD_YES.
+   */
+  GOP_END_YES = 13,
+
+  /**
+   * Processing is done, return #MHD_NO.
+   */
+  GOP_END_NO = 14
+
+};
+
+
 /**
  * Data structure we keep for a check payment request.
  */
 struct GetOrderRequestContext
 {
 
+  /**
+   * Processing phase we are in.
+   */
+  enum GetOrderPhase phase;
+
   /**
    * Entry in the #resume_timeout_heap for this check payment, if we are
    * suspended.
@@ -180,6 +276,21 @@ struct GetOrderRequestContext
    */
   json_t *contract_terms;
 
+  /**
+   * Claim token of the order.
+   */
+  struct TALER_ClaimTokenP claim_token;
+
+  /**
+   * Timestamp from the @e contract_terms.
+   */
+  struct GNUNET_TIME_Timestamp timestamp;
+
+  /**
+   * Order summary. Pointer into @e contract_terms.
+   */
+  const char *summary;
+
   /**
    * Wire details for the payment, to be returned in the reply. NULL
    * if not available.
@@ -273,6 +384,22 @@ struct GetOrderRequestContext
    */
   bool refunded;
 
+  /**
+   * True if the order was paid.
+   */
+  bool paid;
+
+  /**
+   * True if the exchange wired the money to the merchant.
+   */
+  bool wired;
+
+  /**
+   * True if the order remains unclaimed.
+   */
+  bool order_only;
+
+
   /**
    * Set to true if this payment has been refunded and
    * some refunds remain to be picked up by the wallet.
@@ -334,13 +461,9 @@ TMH_force_gorc_resume (void)
  * operations.
  *
  * @param gorc request to resume
- * @param http_status HTTP status to return, 0 to continue with success
- * @param ec error code for the request, #TALER_EC_NONE on success
  */
 static void
-gorc_resume (struct GetOrderRequestContext *gorc,
-             unsigned int http_status,
-             enum TALER_ErrorCode ec)
+gorc_resume (struct GetOrderRequestContext *gorc)
 {
   struct TransferQuery *tq;
 
@@ -362,8 +485,6 @@ gorc_resume (struct GetOrderRequestContext *gorc,
       tq->dgh = NULL;
     }
   }
-  gorc->wire_hc = http_status;
-  gorc->wire_ec = ec;
   GNUNET_assert (GNUNET_YES == gorc->suspended);
   GNUNET_CONTAINER_DLL_remove (gorc_head,
                                gorc_tail,
@@ -374,6 +495,26 @@ gorc_resume (struct GetOrderRequestContext *gorc,
 }
 
 
+/**
+ * Resume processing the request, cancelling all pending asynchronous
+ * operations.
+ *
+ * @param gorc request to resume
+ * @param http_status HTTP status to return, 0 to continue with success
+ * @param ec error code for the request, #TALER_EC_NONE on success
+ */
+static void
+gorc_resume_error (struct GetOrderRequestContext *gorc,
+                   unsigned int http_status,
+                   enum TALER_ErrorCode ec)
+{
+  gorc->wire_hc = http_status;
+  gorc->wire_ec = ec;
+  gorc->phase = GOP_ERROR;
+  gorc_resume (gorc);
+}
+
+
 /**
  * We have received a trigger from the database
  * that we should (possibly) resume the request.
@@ -397,6 +538,7 @@ resume_by_event (void *cls,
   if (GNUNET_NO == gorc->suspended)
     return; /* duplicate event is possible */
   gorc->suspended = GNUNET_NO;
+  gorc->phase = GOP_PARSE_CONTRACT;
   GNUNET_CONTAINER_DLL_remove (gorc_head,
                                gorc_tail,
                                gorc);
@@ -451,404 +593,1023 @@ exchange_timeout_cb (void *cls)
   struct GetOrderRequestContext *gorc = cls;
 
   gorc->tt = NULL;
-  gorc_resume (gorc,
-               MHD_HTTP_REQUEST_TIMEOUT,
-               TALER_EC_GENERIC_TIMEOUT);
+  gorc_resume_error (gorc,
+                     MHD_HTTP_REQUEST_TIMEOUT,
+                     TALER_EC_GENERIC_TIMEOUT);
 }
 
 
 /**
- * Function called with detailed wire transfer data.
+ * Clean up the session state for a GET /private/order/ID request.
  *
- * @param cls closure with a `struct TransferQuery *`
- * @param dr HTTP response data
+ * @param cls closure, must be a `struct GetOrderRequestContext *`
  */
 static void
-deposit_get_cb (void *cls,
-                const struct TALER_EXCHANGE_GetDepositResponse *dr)
+gorc_cleanup (void *cls)
 {
-  struct TransferQuery *tq = cls;
-  struct GetOrderRequestContext *gorc = tq->gorc;
+  struct GetOrderRequestContext *gorc = cls;
 
-  GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
-                               gorc->tq_tail,
-                               tq);
-  switch (dr->hr.http_status)
+  if (NULL != gorc->contract_terms)
+    json_decref (gorc->contract_terms);
+  if (NULL != gorc->wire_details)
+    json_decref (gorc->wire_details);
+  if (NULL != gorc->refund_details)
+    json_decref (gorc->refund_details);
+  if (NULL != gorc->wire_reports)
+    json_decref (gorc->wire_reports);
+  if (NULL != gorc->tt)
   {
-  case MHD_HTTP_OK:
-    {
-      enum GNUNET_DB_QueryStatus qs;
-
-      qs = TMH_db->insert_deposit_to_transfer (TMH_db->cls,
-                                               tq->deposit_serial,
-                                               &dr->details.ok);
-      if (qs < 0)
-      {
-        gorc_report (gorc,
-                     TALER_EC_GENERIC_DB_STORE_FAILED,
-                     &tq->coin_pub,
-                     NULL);
-        GNUNET_free (tq->exchange_url);
-        GNUNET_free (tq);
-        if (NULL == gorc->tq_head)
-          gorc_resume (gorc,
-                       0,
-                       TALER_EC_NONE);
-        return;
-      }
-      /* Compute total amount *wired* */
-      if ( (GNUNET_OK !=
-            TALER_amount_cmp_currency (
-              &gorc->deposits_total,
-              &dr->details.ok.coin_contribution)) ||
-           (GNUNET_OK !=
-            TALER_amount_cmp_currency (
-              &gorc->deposit_fees_total,
-              &tq->deposit_fee)) )
-      {
-        /* something very wrong in our database ... */
-        GNUNET_break (0);
-        gorc_report (gorc,
-                     TALER_EC_GENERIC_DB_FETCH_FAILED,
-                     &tq->coin_pub,
-                     NULL);
-        GNUNET_free (tq->exchange_url);
-        GNUNET_free (tq);
-        if (NULL == gorc->tq_head)
-          gorc_resume (gorc,
-                       0,
-                       TALER_EC_NONE);
-        return;
-      }
-      if (0 >
-          TALER_amount_add (&gorc->deposits_total,
-                            &gorc->deposits_total,
-                            &dr->details.ok.coin_contribution))
-      {
-        gorc_report (gorc,
-                     
TALER_EC_MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE,
-                     &tq->coin_pub,
-                     NULL);
-        GNUNET_free (tq->exchange_url);
-        GNUNET_free (tq);
-        if (NULL == gorc->tq_head)
-          gorc_resume (gorc,
-                       0,
-                       TALER_EC_NONE);
-        return;
-      }
-      if (0 >
-          TALER_amount_add (&gorc->deposit_fees_total,
-                            &gorc->deposit_fees_total,
-                            &tq->deposit_fee))
-      {
-        gorc_report (gorc,
-                     
TALER_EC_MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE,
-                     &tq->coin_pub,
-                     NULL);
-        GNUNET_free (tq->exchange_url);
-        GNUNET_free (tq);
-        if (NULL == gorc->tq_head)
-          gorc_resume (gorc,
-                       0,
-                       TALER_EC_NONE);
-        return;
-      }
-      break;
-    }
-  case MHD_HTTP_ACCEPTED:
-    {
-      /* got a 'preliminary' reply from the exchange,
-         remember our target UUID */
-      enum GNUNET_DB_QueryStatus qs;
-      struct GNUNET_TIME_Timestamp now;
-
-      now = GNUNET_TIME_timestamp_get ();
-      qs = TMH_db->account_kyc_set_status (
-        TMH_db->cls,
-        gorc->hc->instance->settings.id,
-        &tq->h_wire,
-        tq->exchange_url,
-        dr->details.accepted.requirement_row,
-        NULL,
-        NULL,
-        now,
-        dr->details.accepted.kyc_ok,
-        dr->details.accepted.aml_decision);
-      if (qs < 0)
-      {
-        gorc_report (gorc,
-                     TALER_EC_GENERIC_DB_STORE_FAILED,
-                     &tq->coin_pub,
-                     NULL);
-        GNUNET_free (tq->exchange_url);
-        GNUNET_free (tq);
-        if (NULL == gorc->tq_head)
-          gorc_resume (gorc,
-                       0,
-                       TALER_EC_NONE);
-        return;
-      }
-      gorc_report (gorc,
-                   TALER_EC_NONE,
-                   &tq->coin_pub,
-                   &dr->hr);
-      break;
-    }
-  default:
-    {
-      gorc_report (gorc,
-                   TALER_EC_MERCHANT_GET_ORDERS_EXCHANGE_TRACKING_FAILURE,
-                   &tq->coin_pub,
-                   &dr->hr);
-      GNUNET_free (tq->exchange_url);
-      GNUNET_free (tq);
-      if (NULL == gorc->tq_head)
-        gorc_resume (gorc,
-                     0,
-                     TALER_EC_NONE);
-      return;
-    }
-  } /* end switch */
-  GNUNET_free (tq->exchange_url);
-  GNUNET_free (tq);
-  if (NULL != gorc->tq_head)
-    return;
-  /* *all* are done, resume! */
-  gorc_resume (gorc,
-               0,
-               TALER_EC_NONE);
+    GNUNET_SCHEDULER_cancel (gorc->tt);
+    gorc->tt = NULL;
+  }
+  if (NULL != gorc->eh)
+  {
+    TMH_db->event_listen_cancel (gorc->eh);
+    gorc->eh = NULL;
+  }
+  if (NULL != gorc->session_eh)
+  {
+    TMH_db->event_listen_cancel (gorc->session_eh);
+    gorc->session_eh = NULL;
+  }
+  GNUNET_free (gorc);
 }
 
 
 /**
- * Function called with the result of a #TMH_EXCHANGES_keys4exchange()
- * operation.
+ * Processing the request @a gorc is finished, set the
+ * final return value in phase based on @a mret.
  *
- * @param cls closure with a `struct GetOrderRequestContext *`
- * @param keys keys of the exchange
- * @param exchange representation of the exchange
+ * @param[in,out] gorc order context to initialize
+ * @param mret MHD HTTP response status to return
  */
 static void
-exchange_found_cb (void *cls,
-                   struct TALER_EXCHANGE_Keys *keys,
-                   struct TMH_Exchange *exchange)
+phase_end (struct GetOrderRequestContext *gorc,
+           MHD_RESULT mret)
 {
-  struct TransferQuery *tq = cls;
-  struct GetOrderRequestContext *gorc = tq->gorc;
+  gorc->phase = (MHD_YES == mret)
+    ? GOP_END_YES
+    : GOP_END_NO;
+}
 
-  (void) exchange;
-  tq->fo = NULL;
-  if (NULL == keys)
+
+/**
+ * Initialize event callbacks for the order processing.
+ *
+ * @param[in,out] gorc order context to initialize
+ */
+static void
+phase_init (struct GetOrderRequestContext *gorc)
+{
+  struct TMH_HandlerContext *hc = gorc->hc;
+  struct TMH_OrderPayEventP pay_eh = {
+    .header.size = htons (sizeof (pay_eh)),
+    .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
+    .merchant_pub = hc->instance->merchant_pub
+  };
+
+  if (! GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
   {
-    /* failed */
-    GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
-                                 gorc->tq_tail,
-                                 tq);
-    GNUNET_free (tq->exchange_url);
-    GNUNET_free (tq);
-    gorc_resume (gorc,
-                 MHD_HTTP_GATEWAY_TIMEOUT,
-                 TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT);
+    gorc->phase++;
     return;
   }
-  tq->dgh = TALER_EXCHANGE_deposits_get (
-    TMH_curl_ctx,
-    tq->exchange_url,
-    keys,
-    &gorc->hc->instance->merchant_priv,
-    &tq->h_wire,
-    &gorc->h_contract_terms,
-    &tq->coin_pub,
-    GNUNET_TIME_UNIT_ZERO,
-    &deposit_get_cb,
-    tq);
-  if (NULL == tq->dgh)
+
+  GNUNET_CRYPTO_hash (hc->infix,
+                      strlen (hc->infix),
+                      &pay_eh.h_order_id);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Subscribing to payment triggers for %p\n",
+              gorc);
+  gorc->eh = TMH_db->event_listen (
+    TMH_db->cls,
+    &pay_eh.header,
+    GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
+    &resume_by_event,
+    gorc);
+  if ( (NULL != gorc->session_id) &&
+       (NULL != gorc->fulfillment_url) )
   {
-    GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
-                                 gorc->tq_tail,
-                                 tq);
-    GNUNET_free (tq->exchange_url);
-    GNUNET_free (tq);
-    gorc_resume (gorc,
-                 MHD_HTTP_INTERNAL_SERVER_ERROR,
-                 TALER_EC_MERCHANT_GET_ORDERS_ID_EXCHANGE_REQUEST_FAILURE);
+    struct TMH_SessionEventP session_eh = {
+      .header.size = htons (sizeof (session_eh)),
+      .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
+      .merchant_pub = hc->instance->merchant_pub
+    };
+
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Subscribing to session triggers for %p\n",
+                gorc);
+    GNUNET_CRYPTO_hash (gorc->session_id,
+                        strlen (gorc->session_id),
+                        &session_eh.h_session_id);
+    GNUNET_CRYPTO_hash (gorc->fulfillment_url,
+                        strlen (gorc->fulfillment_url),
+                        &session_eh.h_fulfillment_url);
+    gorc->session_eh
+      = TMH_db->event_listen (
+          TMH_db->cls,
+          &session_eh.header,
+          GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
+          &resume_by_event,
+          gorc);
   }
+  gorc->phase++;
 }
 
 
 /**
- * Function called with each @a coin_pub that was deposited into the
- * @a h_wire account of the merchant for the @a deposit_serial as part
- * of the payment for the order identified by @a cls.
+ * Obtain latest contract terms from the database.
  *
- * Queries the exchange for the payment status associated with the
- * given coin.
- *
- * @param cls a `struct GetOrderRequestContext`
- * @param deposit_serial identifies the deposit operation
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param amount_with_fee amount the exchange will deposit for this coin
- * @param deposit_fee fee the exchange will charge for this coin
- * @param h_wire hash of the merchant's wire account into which the deposit 
was made
- * @param coin_pub public key of the deposited coin
+ * @param[in,out] gorc order context to update
  */
 static void
-deposit_cb (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)
+phase_fetch_contract (struct GetOrderRequestContext *gorc)
 {
-  struct GetOrderRequestContext *gorc = cls;
-  struct TransferQuery *tq;
+  struct TMH_HandlerContext *hc = gorc->hc;
+  enum GNUNET_DB_QueryStatus qs;
 
-  tq = GNUNET_new (struct TransferQuery);
-  tq->gorc = gorc;
-  tq->exchange_url = GNUNET_strdup (exchange_url);
-  tq->deposit_serial = deposit_serial;
-  GNUNET_CONTAINER_DLL_insert (gorc->tq_head,
-                               gorc->tq_tail,
-                               tq);
-  tq->coin_pub = *coin_pub;
-  tq->h_wire = *h_wire;
-  tq->amount_with_fee = *amount_with_fee;
-  tq->deposit_fee = *deposit_fee;
-  tq->fo = TMH_EXCHANGES_keys4exchange (exchange_url,
-                                        false,
-                                        &exchange_found_cb,
-                                        tq);
-  if (NULL == tq->fo)
+  if (NULL != gorc->contract_terms)
   {
-    gorc_resume (gorc,
-                 MHD_HTTP_INTERNAL_SERVER_ERROR,
-                 
TALER_EC_MERCHANT_GET_ORDERS_ID_EXCHANGE_LOOKUP_START_FAILURE);
+    /* Free memory filled with old contract terms before fetching the latest
+       ones from the DB.  Note that we cannot simply skip the database
+       interaction as the contract terms loaded previously might be from an
+       earlier *unclaimed* order state (which we loaded in a previous
+       invocation of this function and we are back here due to long polling)
+       and thus the contract terms could have changed during claiming. Thus,
+       we need to fetch the latest contract terms from the DB again. */
+    json_decref (gorc->contract_terms);
+    gorc->contract_terms = NULL;
+    gorc->fulfillment_url = NULL;
+    gorc->summary = NULL;
+  }
+  TMH_db->preflight (TMH_db->cls);
+  qs = TMH_db->lookup_contract_terms (TMH_db->cls,
+                                      hc->instance->settings.id,
+                                      hc->infix,
+                                      &gorc->contract_terms,
+                                      &gorc->order_serial,
+                                      &gorc->paid,
+                                      &gorc->claim_token);
+  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);
+    phase_end (gorc,
+               TALER_MHD_reply_with_error (gorc->sc.con,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "contract terms"));
+    return;
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    gorc->order_only = true;
+  }
+  /* FIXME: what is the point of doing the lookup_order
+     below if order_only is false (qs == 1 above)?
+     Seems we could just return here, or not? */
+  {
+    struct TALER_MerchantPostDataHashP unused;
+    json_t *ct = NULL;
+
+    /* We need the order for two cases:  Either when the contract doesn't 
exist yet,
+     * or when the order is claimed but unpaid, and we need the claim token. */
+    qs = TMH_db->lookup_order (TMH_db->cls,
+                               hc->instance->settings.id,
+                               hc->infix,
+                               &gorc->claim_token,
+                               &unused,
+                               &ct);
+    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);
+      phase_end (gorc,
+                 TALER_MHD_reply_with_error (gorc->sc.con,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                             "order"));
+      return;
+    }
+    if (gorc->order_only &&
+        (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) )
+    {
+      phase_end (gorc,
+                 TALER_MHD_reply_with_error (gorc->sc.con,
+                                             MHD_HTTP_NOT_FOUND,
+                                             
TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
+                                             hc->infix));
+      return;
+    }
+    if (gorc->order_only)
+    {
+      gorc->contract_terms = ct;
+    }
+    else if (NULL != ct)
+    {
+      json_decref (ct);
+    }
   }
+  gorc->phase++;
 }
 
 
 /**
- * Clean up the session state for a GET /private/order/ID request.
+ * Obtain parse contract terms of the order.  Extracts the fulfillment URL,
+ * total amount, summary and timestamp from the contract terms!
  *
- * @param cls closure, must be a `struct GetOrderRequestContext *`
+ * @param[in,out] gorc order context to update
  */
 static void
-gorc_cleanup (void *cls)
+phase_parse_contract (struct GetOrderRequestContext *gorc)
 {
-  struct GetOrderRequestContext *gorc = cls;
+  struct TMH_HandlerContext *hc = gorc->hc;
+  struct GNUNET_JSON_Specification spec[] = {
+    TALER_JSON_spec_amount_any ("amount",
+                                &gorc->contract_amount),
+    GNUNET_JSON_spec_mark_optional (
+      GNUNET_JSON_spec_string ("fulfillment_url",
+                               &gorc->fulfillment_url),
+      NULL),
+    GNUNET_JSON_spec_string ("summary",
+                             &gorc->summary),
+    GNUNET_JSON_spec_timestamp ("timestamp",
+                                &gorc->timestamp),
+    GNUNET_JSON_spec_end ()
+  };
 
-  if (NULL != gorc->contract_terms)
-    json_decref (gorc->contract_terms);
-  if (NULL != gorc->wire_details)
-    json_decref (gorc->wire_details);
-  if (NULL != gorc->refund_details)
-    json_decref (gorc->refund_details);
-  if (NULL != gorc->wire_reports)
-    json_decref (gorc->wire_reports);
-  GNUNET_assert (NULL == gorc->tt);
-  if (NULL != gorc->eh)
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (gorc->contract_terms,
+                         spec,
+                         NULL, NULL))
   {
-    TMH_db->event_listen_cancel (gorc->eh);
-    gorc->eh = NULL;
+    GNUNET_break (0);
+    phase_end (gorc,
+               TALER_MHD_reply_with_error (
+                 gorc->sc.con,
+                 MHD_HTTP_INTERNAL_SERVER_ERROR,
+                 TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+                 hc->infix));
+    return;
   }
-  if (NULL != gorc->session_eh)
+  if (! gorc->order_only)
   {
-    TMH_db->event_listen_cancel (gorc->session_eh);
-    gorc->session_eh = NULL;
+    if (GNUNET_OK !=
+        TALER_JSON_contract_hash (gorc->contract_terms,
+                                  &gorc->h_contract_terms))
+    {
+      GNUNET_break (0);
+      phase_end (gorc,
+                 TALER_MHD_reply_with_error (gorc->sc.con,
+                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                             
TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+                                             NULL));
+      return;
+    }
   }
-  GNUNET_free (gorc);
+  GNUNET_assert (NULL != gorc->contract_terms);
+  gorc->phase++;
 }
 
 
 /**
- * Function called with information about a refund.
- * It is responsible for summing up the refund amount.
+ * Check payment status of the order.
  *
- * @param cls closure
- * @param refund_serial unique serial number of the refund
- * @param timestamp time of the refund (for grouping of refunds in the wallet 
UI)
- * @param coin_pub public coin from which the refund comes from
- * @param exchange_url URL of the exchange that issued @a coin_pub
- * @param rtransaction_id identificator of the refund
- * @param reason human-readable explanation of the refund
- * @param refund_amount refund amount which is being taken from @a coin_pub
- * @param pending true if the this refund was not yet processed by the 
wallet/exchange
+ * @param[in,out] gorc order context to update
  */
 static void
-process_refunds_cb (void *cls,
-                    uint64_t refund_serial,
-                    struct GNUNET_TIME_Timestamp timestamp,
-                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
-                    const char *exchange_url,
-                    uint64_t rtransaction_id,
-                    const char *reason,
-                    const struct TALER_Amount *refund_amount,
-                    bool pending)
+phase_check_paid (struct GetOrderRequestContext *gorc)
 {
-  struct GetOrderRequestContext *gorc = cls;
+  struct TMH_HandlerContext *hc = gorc->hc;
+  enum GNUNET_DB_QueryStatus qs;
 
-  GNUNET_assert (0 ==
-                 json_array_append_new (
-                   gorc->refund_details,
-                   GNUNET_JSON_PACK (
-                     TALER_JSON_pack_amount ("amount",
-                                             refund_amount),
-                     GNUNET_JSON_pack_bool ("pending",
-                                            pending),
-                     GNUNET_JSON_pack_timestamp ("timestamp",
-                                                 timestamp),
-                     GNUNET_JSON_pack_string ("reason",
-                                              reason))));
-  /* For refunded coins, we are not charged deposit fees, so subtract those
-     again */
-  for (struct TransferQuery *tq = gorc->tq_head;
-       NULL != tq;
-       tq = tq->next)
+  if (gorc->order_only)
   {
-    if (0 ==
-        GNUNET_memcmp (&tq->coin_pub,
-                       coin_pub))
-    {
-      if (GNUNET_OK !=
-          TALER_amount_cmp_currency (
-            &gorc->deposit_fees_total,
-            &tq->deposit_fee))
-      {
-        gorc->refund_currency_mismatch = true;
-        return;
-      }
-         
-      GNUNET_assert (0 <=
-                     TALER_amount_subtract (&gorc->deposit_fees_total,
-                                            &gorc->deposit_fees_total,
-                                            &tq->deposit_fee));
-    }
+    gorc->paid = false;
+    gorc->wired = false;
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Order %s unclaimed, no need to lookup payment status\n",
+                hc->infix);
+    gorc->phase++;
+    return;
   }
-  if (GNUNET_OK !=
-      TALER_amount_cmp_currency (
-        &gorc->refund_amount,
-        refund_amount))
+  /* FIXME: why do another DB lookup here, we got 'paid' before already, could
+     have likely gotten 'wired' just as well! */
+  TMH_db->preflight (TMH_db->cls);
+  qs = TMH_db->lookup_payment_status (TMH_db->cls,
+                                      gorc->order_serial,
+                                      gorc->session_id,
+                                      &gorc->paid,
+                                      &gorc->wired);
+  if (0 > qs)
   {
-    gorc->refund_currency_mismatch = true;
+    /* single, read-only SQL statements should never cause
+       serialization problems, and the entry should exist as per above */
+    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+    phase_end (gorc,
+               TALER_MHD_reply_with_error (gorc->sc.con,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "payment status"));
     return;
   }
-  GNUNET_assert (0 <=
-                 TALER_amount_add (&gorc->refund_amount,
-                                   &gorc->refund_amount,
-                                   refund_amount));
-  gorc->refunded = true;
-  gorc->refund_pending |= pending;
+  gorc->phase++;
 }
 
 
 /**
- * Function called with available wire details, to be added to
- * the response.
+ * Check if re-purchase detection applies to the order.
  *
- * @param cls a `struct GetOrderRequestContext`
- * @param wtid wire transfer subject of the wire transfer for the coin
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_check_repurchase (struct GetOrderRequestContext *gorc)
+{
+  struct TMH_HandlerContext *hc = gorc->hc;
+  char *already_paid_order_id = NULL;
+  enum GNUNET_DB_QueryStatus qs;
+  char *taler_pay_uri;
+  char *order_status_url;
+  MHD_RESULT ret;
+
+  if ( (gorc->paid) ||
+       (NULL == gorc->fulfillment_url) ||
+       (NULL == gorc->session_id) )
+  {
+    /* Repurchase cannot apply */
+    gorc->phase++;
+    return;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Running re-purchase detection for %s/%s\n",
+              gorc->session_id,
+              gorc->fulfillment_url);
+  qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
+                                            hc->instance->settings.id,
+                                            gorc->fulfillment_url,
+                                            gorc->session_id,
+                                            &already_paid_order_id);
+  if (0 > qs)
+  {
+    /* single, read-only SQL statements should never cause
+       serialization problems, and the entry should exist as per above */
+    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+    phase_end (gorc,
+               TALER_MHD_reply_with_error (gorc->sc.con,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "order by fulfillment"));
+    return;
+  }
+  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "No already paid order for %s/%s\n",
+                gorc->session_id,
+                gorc->fulfillment_url);
+    gorc->phase++;
+    return;
+  }
+
+  /* User did pay for this order, but under a different session; ask wallet
+       to switch order ID */
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Found already paid order %s\n",
+              already_paid_order_id);
+  taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
+                                          hc->infix,
+                                          gorc->session_id,
+                                          hc->instance->settings.id,
+                                          &gorc->claim_token);
+  order_status_url = TMH_make_order_status_url (gorc->sc.con,
+                                                hc->infix,
+                                                gorc->session_id,
+                                                hc->instance->settings.id,
+                                                &gorc->claim_token,
+                                                NULL);
+  if ( (NULL == taler_pay_uri) ||
+       (NULL == order_status_url) )
+  {
+    GNUNET_break_op (0);
+    GNUNET_free (taler_pay_uri);
+    GNUNET_free (order_status_url);
+    phase_end (gorc,
+               TALER_MHD_reply_with_error (gorc->sc.con,
+                                           MHD_HTTP_BAD_REQUEST,
+                                           
TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
+                                           "host"));
+    return;
+  }
+  ret = TALER_MHD_REPLY_JSON_PACK (
+    gorc->sc.con,
+    MHD_HTTP_OK,
+    GNUNET_JSON_pack_string ("taler_pay_uri",
+                             taler_pay_uri),
+    GNUNET_JSON_pack_string ("order_status_url",
+                             order_status_url),
+    GNUNET_JSON_pack_string ("order_status",
+                             "unpaid"),
+    GNUNET_JSON_pack_string ("already_paid_order_id",
+                             already_paid_order_id),
+    GNUNET_JSON_pack_string ("already_paid_fulfillment_url",
+                             gorc->fulfillment_url),
+    TALER_JSON_pack_amount ("total_amount",
+                            &gorc->contract_amount),
+    GNUNET_JSON_pack_string ("summary",
+                             gorc->summary),
+    GNUNET_JSON_pack_timestamp ("creation_time",
+                                gorc->timestamp));
+  GNUNET_free (taler_pay_uri);
+  GNUNET_free (already_paid_order_id);
+  phase_end (gorc,
+             ret);
+}
+
+
+/**
+ * Check if we should suspend until the order is paid.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_unpaid_finish (struct GetOrderRequestContext *gorc)
+{
+  struct TMH_HandlerContext *hc = gorc->hc;
+  char *taler_pay_uri;
+  char *order_status_url;
+  MHD_RESULT ret;
+
+  if (gorc->paid)
+  {
+    gorc->phase++;
+    return;
+  }
+  /* User never paid for this order, suspend waiting
+     on payment or return details. */
+
+  if (! gorc->order_only)
+  {
+    if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  "Suspending GET /private/orders/%s\n",
+                  hc->infix);
+      GNUNET_CONTAINER_DLL_insert (gorc_head,
+                                   gorc_tail,
+                                   gorc);
+      gorc->phase = GOP_SUSPENDED_ON_UNPAID;
+      gorc->suspended = GNUNET_YES;
+      MHD_suspend_connection (gorc->sc.con);
+      return;
+    }
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Order %s claimed but not paid yet\n",
+                hc->infix);
+    phase_end (gorc,
+               TALER_MHD_REPLY_JSON_PACK (
+                 gorc->sc.con,
+                 MHD_HTTP_OK,
+                 GNUNET_JSON_pack_object_incref ("contract_terms",
+                                                 gorc->contract_terms),
+                 GNUNET_JSON_pack_string ("order_status",
+                                          "claimed")));
+    return;
+  }
+
+  /* FIXME: too similar to logic above! */
+  if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Suspending GET /private/orders/%s\n",
+                hc->infix);
+    GNUNET_assert (GNUNET_NO == gorc->suspended);
+    GNUNET_CONTAINER_DLL_insert (gorc_head,
+                                 gorc_tail,
+                                 gorc);
+    gorc->suspended = GNUNET_YES;
+    gorc->phase = GOP_SUSPENDED_ON_UNPAID;
+    MHD_suspend_connection (gorc->sc.con);
+    return;
+  }
+  taler_pay_uri = TMH_make_taler_pay_uri (gorc->sc.con,
+                                          hc->infix,
+                                          gorc->session_id,
+                                          hc->instance->settings.id,
+                                          &gorc->claim_token);
+  order_status_url = TMH_make_order_status_url (gorc->sc.con,
+                                                hc->infix,
+                                                gorc->session_id,
+                                                hc->instance->settings.id,
+                                                &gorc->claim_token,
+                                                NULL);
+  ret = TALER_MHD_REPLY_JSON_PACK (
+    gorc->sc.con,
+    MHD_HTTP_OK,
+    GNUNET_JSON_pack_string ("taler_pay_uri",
+                             taler_pay_uri),
+    GNUNET_JSON_pack_string ("order_status_url",
+                             order_status_url),
+    GNUNET_JSON_pack_string ("order_status",
+                             "unpaid"),
+    TALER_JSON_pack_amount ("total_amount",
+                            &gorc->contract_amount),
+    GNUNET_JSON_pack_string ("summary",
+                             gorc->summary),
+    GNUNET_JSON_pack_timestamp ("creation_time",
+                                gorc->timestamp));
+  GNUNET_free (taler_pay_uri);
+  GNUNET_free (order_status_url);
+  phase_end (gorc,
+             ret);
+
+}
+
+
+/**
+ * Function called with information about a refund.
+ * It is responsible for summing up the refund amount.
+ *
+ * @param cls closure
+ * @param refund_serial unique serial number of the refund
+ * @param timestamp time of the refund (for grouping of refunds in the wallet 
UI)
+ * @param coin_pub public coin from which the refund comes from
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param rtransaction_id identificator of the refund
+ * @param reason human-readable explanation of the refund
+ * @param refund_amount refund amount which is being taken from @a coin_pub
+ * @param pending true if the this refund was not yet processed by the 
wallet/exchange
+ */
+static void
+process_refunds_cb (void *cls,
+                    uint64_t refund_serial,
+                    struct GNUNET_TIME_Timestamp timestamp,
+                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                    const char *exchange_url,
+                    uint64_t rtransaction_id,
+                    const char *reason,
+                    const struct TALER_Amount *refund_amount,
+                    bool pending)
+{
+  struct GetOrderRequestContext *gorc = cls;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Found refund %llu over %s for reason %s\n",
+              (unsigned long long) rtransaction_id,
+              TALER_amount2s (refund_amount),
+              reason);
+  GNUNET_assert (0 ==
+                 json_array_append_new (
+                   gorc->refund_details,
+                   GNUNET_JSON_PACK (
+                     TALER_JSON_pack_amount ("amount",
+                                             refund_amount),
+                     GNUNET_JSON_pack_bool ("pending",
+                                            pending),
+                     GNUNET_JSON_pack_timestamp ("timestamp",
+                                                 timestamp),
+                     GNUNET_JSON_pack_string ("reason",
+                                              reason))));
+  /* For refunded coins, we are not charged deposit fees, so subtract those
+     again */
+  for (struct TransferQuery *tq = gorc->tq_head;
+       NULL != tq;
+       tq = tq->next)
+  {
+    if (0 ==
+        GNUNET_memcmp (&tq->coin_pub,
+                       coin_pub))
+    {
+      if (GNUNET_OK !=
+          TALER_amount_cmp_currency (
+            &gorc->deposit_fees_total,
+            &tq->deposit_fee))
+      {
+        gorc->refund_currency_mismatch = true;
+        return;
+      }
+
+      GNUNET_assert (0 <=
+                     TALER_amount_subtract (&gorc->deposit_fees_total,
+                                            &gorc->deposit_fees_total,
+                                            &tq->deposit_fee));
+    }
+  }
+  if (GNUNET_OK !=
+      TALER_amount_cmp_currency (
+        &gorc->refund_amount,
+        refund_amount))
+  {
+    gorc->refund_currency_mismatch = true;
+    return;
+  }
+  GNUNET_assert (0 <=
+                 TALER_amount_add (&gorc->refund_amount,
+                                   &gorc->refund_amount,
+                                   refund_amount));
+  gorc->refunded = true;
+  gorc->refund_pending |= pending;
+}
+
+
+/**
+ * Check refund status for the order.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_check_refunds (struct GetOrderRequestContext *gorc)
+{
+  struct TMH_HandlerContext *hc = gorc->hc;
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_assert (! gorc->order_only);
+  GNUNET_assert (gorc->paid);
+  /* Accumulate refunds, if any. */
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (gorc->contract_amount.currency,
+                                        &gorc->refund_amount));
+  qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
+                                        hc->instance->settings.id,
+                                        &gorc->h_contract_terms,
+                                        &process_refunds_cb,
+                                        gorc);
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    phase_end (gorc,
+               TALER_MHD_reply_with_error (gorc->sc.con,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "detailed refunds"));
+    return;
+  }
+  if (gorc->refund_currency_mismatch)
+  {
+    GNUNET_break (0);
+    phase_end (gorc,
+               TALER_MHD_reply_with_error (gorc->sc.con,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "refunds in different currency than 
original order price"));
+    return;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Total refunds are %s\n",
+              TALER_amount2s (&gorc->refund_amount));
+  gorc->phase++;
+}
+
+
+/**
+ * Function called with detailed wire transfer data.
+ *
+ * @param cls closure with a `struct TransferQuery *`
+ * @param dr HTTP response data
+ */
+static void
+deposit_get_cb (void *cls,
+                const struct TALER_EXCHANGE_GetDepositResponse *dr)
+{
+  struct TransferQuery *tq = cls;
+  struct GetOrderRequestContext *gorc = tq->gorc;
+
+  GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
+                               gorc->tq_tail,
+                               tq);
+  switch (dr->hr.http_status)
+  {
+  case MHD_HTTP_OK:
+    {
+      enum GNUNET_DB_QueryStatus qs;
+
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Exchange returned wire transfer over %s for deposited coin 
%s\n",
+                  TALER_amount2s (&dr->details.ok.coin_contribution),
+                  TALER_B2S (&tq->coin_pub));
+      qs = TMH_db->insert_deposit_to_transfer (TMH_db->cls,
+                                               tq->deposit_serial,
+                                               &dr->details.ok);
+      if (qs < 0)
+      {
+        gorc_report (gorc,
+                     TALER_EC_GENERIC_DB_STORE_FAILED,
+                     &tq->coin_pub,
+                     NULL);
+        GNUNET_free (tq->exchange_url);
+        GNUNET_free (tq);
+        if (NULL == gorc->tq_head)
+        {
+          gorc->phase++;
+          gorc_resume (gorc);
+        }
+        return;
+      }
+      /* Compute total amount *wired* */
+      if ( (GNUNET_OK !=
+            TALER_amount_cmp_currency (
+              &gorc->deposits_total,
+              &dr->details.ok.coin_contribution)) ||
+           (GNUNET_OK !=
+            TALER_amount_cmp_currency (
+              &gorc->deposit_fees_total,
+              &tq->deposit_fee)) )
+      {
+        /* something very wrong in our database ... */
+        GNUNET_break (0);
+        gorc_report (gorc,
+                     TALER_EC_GENERIC_DB_FETCH_FAILED,
+                     &tq->coin_pub,
+                     NULL);
+        GNUNET_free (tq->exchange_url);
+        GNUNET_free (tq);
+        if (NULL == gorc->tq_head)
+        {
+          gorc->phase++;
+          gorc_resume (gorc);
+        }
+        return;
+      }
+      if (0 >
+          TALER_amount_add (&gorc->deposits_total,
+                            &gorc->deposits_total,
+                            &dr->details.ok.coin_contribution))
+      {
+        gorc_report (gorc,
+                     
TALER_EC_MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE,
+                     &tq->coin_pub,
+                     NULL);
+        GNUNET_free (tq->exchange_url);
+        GNUNET_free (tq);
+        if (NULL == gorc->tq_head)
+        {
+          gorc->phase++;
+          gorc_resume (gorc);
+        }
+        return;
+      }
+      if (0 >
+          TALER_amount_add (&gorc->deposit_fees_total,
+                            &gorc->deposit_fees_total,
+                            &tq->deposit_fee))
+      {
+        gorc_report (gorc,
+                     
TALER_EC_MERCHANT_PRIVATE_GET_ORDERS_ID_AMOUNT_ARITHMETIC_FAILURE,
+                     &tq->coin_pub,
+                     NULL);
+        GNUNET_free (tq->exchange_url);
+        GNUNET_free (tq);
+        if (NULL == gorc->tq_head)
+        {
+          gorc->phase++;
+          gorc_resume (gorc);
+        }
+        return;
+      }
+      break;
+    }
+  case MHD_HTTP_ACCEPTED:
+    {
+      /* got a 'preliminary' reply from the exchange,
+         remember our target UUID */
+      enum GNUNET_DB_QueryStatus qs;
+      struct GNUNET_TIME_Timestamp now;
+
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Exchange returned KYC requirement (%d/%d) for deposited 
coin %s\n",
+                  dr->details.accepted.kyc_ok,
+                  dr->details.accepted.aml_decision,
+                  TALER_B2S (&tq->coin_pub));
+      now = GNUNET_TIME_timestamp_get ();
+      qs = TMH_db->account_kyc_set_status (
+        TMH_db->cls,
+        gorc->hc->instance->settings.id,
+        &tq->h_wire,
+        tq->exchange_url,
+        dr->details.accepted.requirement_row,
+        NULL,
+        NULL,
+        now,
+        dr->details.accepted.kyc_ok,
+        dr->details.accepted.aml_decision);
+      if (qs < 0)
+      {
+        gorc_report (gorc,
+                     TALER_EC_GENERIC_DB_STORE_FAILED,
+                     &tq->coin_pub,
+                     NULL);
+        GNUNET_free (tq->exchange_url);
+        GNUNET_free (tq);
+        if (NULL == gorc->tq_head)
+        {
+          gorc->phase++;
+          gorc_resume (gorc);
+        }
+        return;
+      }
+      gorc_report (gorc,
+                   TALER_EC_NONE,
+                   &tq->coin_pub,
+                   &dr->hr);
+      break;
+    }
+  default:
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Exchange returned tracking failure for deposited coin %s\n",
+                  TALER_B2S (&tq->coin_pub));
+      gorc_report (gorc,
+                   TALER_EC_MERCHANT_GET_ORDERS_EXCHANGE_TRACKING_FAILURE,
+                   &tq->coin_pub,
+                   &dr->hr);
+      GNUNET_free (tq->exchange_url);
+      GNUNET_free (tq);
+      if (NULL == gorc->tq_head)
+      {
+        gorc->phase++;
+        gorc_resume (gorc);
+      }
+      return;
+    }
+  } /* end switch */
+  GNUNET_free (tq->exchange_url);
+  GNUNET_free (tq);
+  if (NULL != gorc->tq_head)
+    return;
+  /* *all* are done, resume! */
+  gorc->phase++;
+  gorc_resume (gorc);
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_keys4exchange()
+ * operation.
+ *
+ * @param cls closure with a `struct GetOrderRequestContext *`
+ * @param keys keys of the exchange
+ * @param exchange representation of the exchange
+ */
+static void
+exchange_found_cb (void *cls,
+                   struct TALER_EXCHANGE_Keys *keys,
+                   struct TMH_Exchange *exchange)
+{
+  struct TransferQuery *tq = cls;
+  struct GetOrderRequestContext *gorc = tq->gorc;
+
+  (void) exchange;
+  tq->fo = NULL;
+  if (NULL == keys)
+  {
+    /* failed */
+    GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
+                                 gorc->tq_tail,
+                                 tq);
+    GNUNET_free (tq->exchange_url);
+    GNUNET_free (tq);
+    gorc_resume_error (gorc,
+                       MHD_HTTP_GATEWAY_TIMEOUT,
+                       TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT);
+    return;
+  }
+  tq->dgh = TALER_EXCHANGE_deposits_get (
+    TMH_curl_ctx,
+    tq->exchange_url,
+    keys,
+    &gorc->hc->instance->merchant_priv,
+    &tq->h_wire,
+    &gorc->h_contract_terms,
+    &tq->coin_pub,
+    GNUNET_TIME_UNIT_ZERO,
+    &deposit_get_cb,
+    tq);
+  if (NULL == tq->dgh)
+  {
+    GNUNET_CONTAINER_DLL_remove (gorc->tq_head,
+                                 gorc->tq_tail,
+                                 tq);
+    GNUNET_free (tq->exchange_url);
+    GNUNET_free (tq);
+    gorc_resume_error (gorc,
+                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                       
TALER_EC_MERCHANT_GET_ORDERS_ID_EXCHANGE_REQUEST_FAILURE);
+  }
+}
+
+
+/**
+ * Function called with each @a coin_pub that was deposited into the
+ * @a h_wire account of the merchant for the @a deposit_serial as part
+ * of the payment for the order identified by @a cls.
+ *
+ * Queries the exchange for the payment status associated with the
+ * given coin.
+ *
+ * @param cls a `struct GetOrderRequestContext`
+ * @param deposit_serial identifies the deposit operation
+ * @param exchange_url URL of the exchange that issued @a coin_pub
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param h_wire hash of the merchant's wire account into which the deposit 
was made
+ * @param coin_pub public key of the deposited coin
+ */
+static void
+deposit_cb (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 GetOrderRequestContext *gorc = cls;
+  struct TransferQuery *tq;
+
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Checking deposit status for coin %s (over %s)\n",
+              TALER_B2S (coin_pub),
+              TALER_amount2s (amount_with_fee));
+  tq = GNUNET_new (struct TransferQuery);
+  tq->gorc = gorc;
+  tq->exchange_url = GNUNET_strdup (exchange_url);
+  tq->deposit_serial = deposit_serial;
+  GNUNET_CONTAINER_DLL_insert (gorc->tq_head,
+                               gorc->tq_tail,
+                               tq);
+  tq->coin_pub = *coin_pub;
+  tq->h_wire = *h_wire;
+  tq->amount_with_fee = *amount_with_fee;
+  tq->deposit_fee = *deposit_fee;
+  tq->fo = TMH_EXCHANGES_keys4exchange (exchange_url,
+                                        false,
+                                        &exchange_found_cb,
+                                        tq);
+  if (NULL == tq->fo)
+  {
+    gorc_resume_error (gorc,
+                       MHD_HTTP_INTERNAL_SERVER_ERROR,
+                       
TALER_EC_MERCHANT_GET_ORDERS_ID_EXCHANGE_LOOKUP_START_FAILURE);
+  }
+}
+
+
+/**
+ * Check wire transfer status for the order at the exchange.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_check_exchange_transfers (struct GetOrderRequestContext *gorc)
+{
+  if (gorc->wired ||
+      (! gorc->transfer_status_requested) )
+  {
+    gorc->phase = GOP_CHECK_LOCAL_TRANSFERS;
+    return;
+  }
+  /* suspend connection, wait for exchange to check wire transfer status there 
*/
+  gorc->transfer_status_requested = false;     /* only try ONCE */
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (gorc->contract_amount.currency,
+                                        &gorc->deposits_total));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (gorc->contract_amount.currency,
+                                        &gorc->deposit_fees_total));
+  TMH_db->lookup_deposits_by_order (TMH_db->cls,
+                                    gorc->order_serial,
+                                    &deposit_cb,
+                                    gorc);
+  if (NULL == gorc->tq_head)
+  {
+    /* No deposits found for paid order. This is strange... */
+    GNUNET_break (0);
+    gorc->phase = GOP_CHECK_LOCAL_TRANSFERS;
+    return;
+  }
+  gorc->phase++;
+  GNUNET_CONTAINER_DLL_insert (gorc_head,
+                               gorc_tail,
+                               gorc);
+  gorc->suspended = GNUNET_YES;
+  MHD_suspend_connection (gorc->sc.con);
+  gorc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
+                                           &exchange_timeout_cb,
+                                           gorc);
+}
+
+
+/**
+ * Function called with available wire details, to be added to
+ * the response.
+ *
+ * @param cls a `struct GetOrderRequestContext`
+ * @param wtid wire transfer subject of the wire transfer for the coin
  * @param exchange_url base URL of the exchange that made the payment
  * @param execution_time when was the payment made
  * @param deposit_value contribution of the coin to the total wire transfer 
value
@@ -881,7 +1642,7 @@ process_transfer_details (
     gorc->deposit_currency_mismatch = true;
     return;
   }
-       
+
   /* Compute total amount *wired* */
   GNUNET_assert (0 <
                  TALER_amount_add (&gorc->deposits_total,
@@ -913,19 +1674,202 @@ process_transfer_details (
 }
 
 
+/**
+ * Check transfer status in local database.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_check_local_transfers (struct GetOrderRequestContext *gorc)
+{
+  struct TMH_HandlerContext *hc = gorc->hc;
+  enum GNUNET_DB_QueryStatus qs;
+
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (gorc->contract_amount.currency,
+                                        &gorc->deposits_total));
+  GNUNET_assert (GNUNET_OK ==
+                 TALER_amount_set_zero (gorc->contract_amount.currency,
+                                        &gorc->deposit_fees_total));
+  qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
+                                                 gorc->order_serial,
+                                                 &process_transfer_details,
+                                                 gorc);
+  if (0 > qs)
+  {
+    GNUNET_break (0);
+    phase_end (gorc,
+               TALER_MHD_reply_with_error (gorc->sc.con,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "transfer details"));
+    return;
+  }
+  if (gorc->deposit_currency_mismatch)
+  {
+    GNUNET_break (0);
+    phase_end (gorc,
+               TALER_MHD_reply_with_error (gorc->sc.con,
+                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
+                                           "deposits in different currency 
than original order price"));
+    return;
+  }
+
+  if (! gorc->wired)
+  {
+    /* we believe(d) the wire transfer did not happen yet, check if maybe
+       in light of new evidence it did */
+    struct TALER_Amount expect_total;
+
+    if (0 >
+        TALER_amount_subtract (&expect_total,
+                               &gorc->contract_amount,
+                               &gorc->refund_amount))
+    {
+      GNUNET_break (0);
+      phase_end (gorc,
+                 TALER_MHD_reply_with_error (
+                   gorc->sc.con,
+                   MHD_HTTP_INTERNAL_SERVER_ERROR,
+                   TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+                   "refund exceeds contract value"));
+      return;
+    }
+    if (0 >
+        TALER_amount_subtract (&expect_total,
+                               &expect_total,
+                               &gorc->deposit_fees_total))
+    {
+      GNUNET_break (0);
+      phase_end (gorc,
+                 TALER_MHD_reply_with_error (
+                   gorc->sc.con,
+                   MHD_HTTP_INTERNAL_SERVER_ERROR,
+                   TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+                   "deposit fees exceed total minus refunds"));
+      return;
+    }
+    if (0 >=
+        TALER_amount_cmp (&expect_total,
+                          &gorc->deposits_total))
+    {
+      /* expect_total <= gorc->deposits_total: good: we got the wire transfer 
*/
+      gorc->wired = true;
+      qs = TMH_db->mark_order_wired (TMH_db->cls,
+                                     gorc->order_serial);
+      GNUNET_break (qs >= 0);   /* just warn if transaction failed */
+      TMH_notify_order_change (hc->instance,
+                               TMH_OSF_PAID
+                               | TMH_OSF_WIRED,
+                               gorc->timestamp,
+                               gorc->order_serial);
+    }
+  }
+  gorc->phase++;
+}
+
+
+/**
+ * Generate final result for the status request.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_reply_result (struct GetOrderRequestContext *gorc)
+{
+  struct TMH_HandlerContext *hc = gorc->hc;
+  MHD_RESULT ret;
+  char *order_status_url;
+
+  {
+    struct TALER_PrivateContractHashP *h_contract = NULL;
+
+    /* In a session-bound payment, allow the browser to check the order
+     * status page (e.g. to get a refund).
+     *
+     * Note that we don't allow this outside of session-based payment, as
+     * otherwise this becomes an oracle to convert order_id to h_contract.
+     */
+    if (NULL != gorc->session_id)
+      h_contract = &gorc->h_contract_terms;
+
+    order_status_url =
+      TMH_make_order_status_url (gorc->sc.con,
+                                 hc->infix,
+                                 gorc->session_id,
+                                 hc->instance->settings.id,
+                                 &gorc->claim_token,
+                                 h_contract);
+  }
+
+  ret = TALER_MHD_REPLY_JSON_PACK (
+    gorc->sc.con,
+    MHD_HTTP_OK,
+    GNUNET_JSON_pack_array_steal ("wire_reports",
+                                  gorc->wire_reports),
+    GNUNET_JSON_pack_uint64 ("exchange_code",
+                             gorc->exchange_ec),
+    GNUNET_JSON_pack_uint64 ("exchange_http_status",
+                             gorc->exchange_hc),
+    /* legacy: */
+    GNUNET_JSON_pack_uint64 ("exchange_ec",
+                             gorc->exchange_ec),
+    /* legacy: */
+    GNUNET_JSON_pack_uint64 ("exchange_hc",
+                             gorc->exchange_hc),
+    TALER_JSON_pack_amount ("deposit_total",
+                            &gorc->deposits_total),
+    GNUNET_JSON_pack_object_incref ("contract_terms",
+                                    gorc->contract_terms),
+    GNUNET_JSON_pack_string ("order_status",
+                             "paid"),
+    GNUNET_JSON_pack_bool ("refunded",
+                           gorc->refunded),
+    GNUNET_JSON_pack_bool ("wired",
+                           gorc->wired),
+    GNUNET_JSON_pack_bool ("refund_pending",
+                           gorc->refund_pending),
+    TALER_JSON_pack_amount ("refund_amount",
+                            &gorc->refund_amount),
+    GNUNET_JSON_pack_array_steal ("wire_details",
+                                  gorc->wire_details),
+    GNUNET_JSON_pack_array_steal ("refund_details",
+                                  gorc->refund_details),
+    GNUNET_JSON_pack_string ("order_status_url",
+                             order_status_url));
+  GNUNET_free (order_status_url);
+  gorc->wire_details = NULL;
+  gorc->wire_reports = NULL;
+  gorc->refund_details = NULL;
+  phase_end (gorc,
+             ret);
+}
+
+
+/**
+ * End with error status in wire_hc and wire_ec.
+ *
+ * @param[in,out] gorc order context to update
+ */
+static void
+phase_error (struct GetOrderRequestContext *gorc)
+{
+  GNUNET_assert (TALER_EC_NONE != gorc->wire_ec);
+  phase_end (gorc,
+             TALER_MHD_reply_with_error (gorc->sc.con,
+                                         gorc->wire_hc,
+                                         gorc->wire_ec,
+                                         NULL));
+}
+
+
 MHD_RESULT
 TMH_private_get_orders_ID (const struct TMH_RequestHandler *rh,
                            struct MHD_Connection *connection,
                            struct TMH_HandlerContext *hc)
 {
   struct GetOrderRequestContext *gorc = hc->ctx;
-  enum GNUNET_DB_QueryStatus qs;
-  bool paid;
-  bool wired;
-  bool order_only = false;
-  struct TALER_ClaimTokenP claim_token = { 0 };
-  const char *summary;
-  struct GNUNET_TIME_Timestamp timestamp;
 
   if (NULL == gorc)
   {
@@ -960,590 +1904,69 @@ TMH_private_get_orders_ID (const struct 
TMH_RequestHandler *rh,
 
     TALER_MHD_parse_request_timeout (connection,
                                      &gorc->sc.long_poll_timeout);
-    if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
-    {
-      struct TMH_OrderPayEventP pay_eh = {
-        .header.size = htons (sizeof (pay_eh)),
-        .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
-        .merchant_pub = hc->instance->merchant_pub
-      };
-
-      GNUNET_CRYPTO_hash (hc->infix,
-                          strlen (hc->infix),
-                          &pay_eh.h_order_id);
-      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                  "Subscribing to payment triggers for %p\n",
-                  gorc);
-      gorc->eh = TMH_db->event_listen (
-        TMH_db->cls,
-        &pay_eh.header,
-        GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
-        &resume_by_event,
-        gorc);
-      if ( (NULL != gorc->session_id) &&
-           (NULL != gorc->fulfillment_url) )
-      {
-        struct TMH_SessionEventP session_eh = {
-          .header.size = htons (sizeof (session_eh)),
-          .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
-          .merchant_pub = hc->instance->merchant_pub
-        };
-
-        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                    "Subscribing to session triggers for %p\n",
-                    gorc);
-        GNUNET_CRYPTO_hash (gorc->session_id,
-                            strlen (gorc->session_id),
-                            &session_eh.h_session_id);
-        GNUNET_CRYPTO_hash (gorc->fulfillment_url,
-                            strlen (gorc->fulfillment_url),
-                            &session_eh.h_fulfillment_url);
-        gorc->session_eh
-          = TMH_db->event_listen (
-              TMH_db->cls,
-              &session_eh.header,
-              GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout),
-              &resume_by_event,
-              gorc);
-      }
-    }
-  } /* end first-time per-request initialization */
-
-  if (GNUNET_SYSERR == gorc->suspended)
-    return MHD_NO; /* we are in shutdown */
-
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-              "Starting GET /private/orders/%s processing with timeout %s\n",
-              hc->infix,
-              GNUNET_STRINGS_absolute_time_to_string (
-                gorc->sc.long_poll_timeout));
-  if (NULL != gorc->contract_terms)
-  {
-    /* Free memory filled with old contract terms before fetching the latest
-       ones from the DB.  Note that we cannot simply skip the database
-       interaction as the contract terms loaded previously might be from an
-       earlier *unclaimed* order state (which we loaded in a previous
-       invocation of this function and we are back here due to long polling)
-       and thus the contract terms could have changed during claiming. Thus,
-       we need to fetch the latest contract terms from the DB again. */
-    json_decref (gorc->contract_terms);
-    gorc->contract_terms = NULL;
-    gorc->fulfillment_url = NULL;
-  }
-  TMH_db->preflight (TMH_db->cls);
-  {
-    bool paid = false;
-
-    qs = TMH_db->lookup_contract_terms (TMH_db->cls,
-                                        hc->instance->settings.id,
-                                        hc->infix,
-                                        &gorc->contract_terms,
-                                        &gorc->order_serial,
-                                        &paid,
-                                        NULL);
-  }
-  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
-  {
-    order_only = true;
-  }
-  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);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                       "contract terms");
-  }
-
-  {
-    struct TALER_MerchantPostDataHashP unused;
-    json_t *ct = NULL;
-
-    /* We need the order for two cases:  Either when the contract doesn't 
exist yet,
-     * or when the order is claimed but unpaid, and we need the claim token. */
-    qs = TMH_db->lookup_order (TMH_db->cls,
-                               hc->instance->settings.id,
-                               hc->infix,
-                               &claim_token,
-                               &unused,
-                               &ct);
-
-    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);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                         "order");
-    }
-    if (order_only && (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) )
-    {
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_NOT_FOUND,
-                                         
TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
-                                         hc->infix);
-    }
-    if (order_only)
-    {
-      gorc->contract_terms = ct;
-    }
-    else if (NULL != ct)
-    {
-      json_decref (ct);
-    }
-  }
-  /* extract the fulfillment URL, total amount, summary and timestamp
-     from the contract terms! */
-  {
-    struct GNUNET_JSON_Specification spec[] = {
-      TALER_JSON_spec_amount_any ("amount",
-                                  &gorc->contract_amount),
-      GNUNET_JSON_spec_mark_optional (
-        GNUNET_JSON_spec_string ("fulfillment_url",
-                                 &gorc->fulfillment_url),
-        NULL),
-      GNUNET_JSON_spec_string ("summary",
-                               &summary),
-      GNUNET_JSON_spec_timestamp ("timestamp",
-                                  &timestamp),
-      GNUNET_JSON_spec_end ()
-    };
-
-    if (GNUNET_OK !=
-        GNUNET_JSON_parse (gorc->contract_terms,
-                           spec,
-                           NULL, NULL))
-    {
-      GNUNET_break (0);
-      return TALER_MHD_reply_with_error (
-        connection,
-        MHD_HTTP_INTERNAL_SERVER_ERROR,
-        TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
-        hc->infix);
-    }
-  }
-  if (! order_only)
-  {
-    if (GNUNET_OK !=
-        TALER_JSON_contract_hash (gorc->contract_terms,
-                                  &gorc->h_contract_terms))
-    {
-      GNUNET_break (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         
TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
-                                         NULL);
-    }
-  }
-  if (TALER_EC_NONE != gorc->wire_ec)
-  {
-    return TALER_MHD_reply_with_error (connection,
-                                       gorc->wire_hc,
-                                       gorc->wire_ec,
-                                       NULL);
-  }
-
-  GNUNET_assert (NULL != gorc->contract_terms);
-
-  TMH_db->preflight (TMH_db->cls);
-  if (order_only)
-  {
-    paid = false;
-    wired = false;
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Order %s unclaimed, no need to lookup payment status\n",
-                hc->infix);
-  }
-  else
-  {
-    qs = TMH_db->lookup_payment_status (TMH_db->cls,
-                                        gorc->order_serial,
-                                        gorc->session_id,
-                                        &paid,
-                                        &wired);
-    if (0 > qs)
-    {
-      /* single, read-only SQL statements should never cause
-         serialization problems, and the entry should exist as per above */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                         "payment status");
-    }
-  }
-  if ( (! paid) &&
-       (NULL != gorc->fulfillment_url) &&
-       (NULL != gorc->session_id) )
-  {
-    char *already_paid_order_id = NULL;
-
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Running re-purchase detection for %s/%s\n",
-                gorc->session_id,
-                gorc->fulfillment_url);
-    qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls,
-                                              hc->instance->settings.id,
-                                              gorc->fulfillment_url,
-                                              gorc->session_id,
-                                              &already_paid_order_id);
-    if (0 > qs)
-    {
-      /* single, read-only SQL statements should never cause
-         serialization problems, and the entry should exist as per above */
-      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                         "order by fulfillment");
-    }
-    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
-    {
-      /* User did pay for this order, but under a different session; ask wallet
-         to switch order ID */
-      char *taler_pay_uri;
-      char *order_status_url;
-      MHD_RESULT ret;
-
-      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                  "Found already paid order %s\n",
-                  already_paid_order_id);
-      taler_pay_uri = TMH_make_taler_pay_uri (connection,
-                                              hc->infix,
-                                              gorc->session_id,
-                                              hc->instance->settings.id,
-                                              &claim_token);
-      order_status_url = TMH_make_order_status_url (connection,
-                                                    hc->infix,
-                                                    gorc->session_id,
-                                                    hc->instance->settings.id,
-                                                    &claim_token,
-                                                    NULL);
-      if ( (NULL == taler_pay_uri) ||
-           (NULL == order_status_url) )
-      {
-        GNUNET_break_op (0);
-        GNUNET_free (taler_pay_uri);
-        GNUNET_free (order_status_url);
-        return TALER_MHD_reply_with_error (connection,
-                                           MHD_HTTP_BAD_REQUEST,
-                                           
TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
-                                           "host");
-      }
-      ret = TALER_MHD_REPLY_JSON_PACK (
-        connection,
-        MHD_HTTP_OK,
-        GNUNET_JSON_pack_string ("taler_pay_uri",
-                                 taler_pay_uri),
-        GNUNET_JSON_pack_string ("order_status_url",
-                                 order_status_url),
-        GNUNET_JSON_pack_string ("order_status",
-                                 "unpaid"),
-        GNUNET_JSON_pack_string ("already_paid_order_id",
-                                 already_paid_order_id),
-        GNUNET_JSON_pack_string ("already_paid_fulfillment_url",
-                                 gorc->fulfillment_url),
-        TALER_JSON_pack_amount ("total_amount",
-                                &gorc->contract_amount),
-        GNUNET_JSON_pack_string ("summary",
-                                 summary),
-        GNUNET_JSON_pack_timestamp ("creation_time",
-                                    timestamp));
-      GNUNET_free (taler_pay_uri);
-      GNUNET_free (already_paid_order_id);
-      return ret;
-    }
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "No already paid order for %s/%s\n",
-                gorc->session_id,
-                gorc->fulfillment_url);
+                "Starting GET /private/orders/%s processing with timeout %s\n",
+                hc->infix,
+                GNUNET_STRINGS_absolute_time_to_string (
+                  gorc->sc.long_poll_timeout));
   }
-  if ( (! paid) &&
-       (! order_only) )
+  if (GNUNET_SYSERR == gorc->suspended)
+    return MHD_NO; /* we are in shutdown */
+  while (1)
   {
-    if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout))
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Processing order %s in phase %d\n",
+                hc->infix,
+                (int) gorc->phase);
+    switch (gorc->phase)
     {
-      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                  "Suspending GET /private/orders/%s\n",
-                  hc->infix);
-      GNUNET_CONTAINER_DLL_insert (gorc_head,
-                                   gorc_tail,
-                                   gorc);
-      gorc->suspended = GNUNET_YES;
-      MHD_suspend_connection (gorc->sc.con);
+    case GOP_INIT:
+      phase_init (gorc);
+      break;
+    case GOP_FETCH_CONTRACT:
+      phase_fetch_contract (gorc);
+      break;
+    case GOP_PARSE_CONTRACT:
+      phase_parse_contract (gorc);
+      break;
+    case GOP_CHECK_PAID:
+      phase_check_paid (gorc);
+      break;
+    case GOP_CHECK_REPURCHASE:
+      phase_check_repurchase (gorc);
+      break;
+    case GOP_UNPAID_FINISH:
+      phase_unpaid_finish (gorc);
+      break;
+    case GOP_CHECK_REFUNDS:
+      phase_check_refunds (gorc);
+      break;
+    case GOP_CHECK_EXCHANGE_TRANSFERS:
+      phase_check_exchange_transfers (gorc);
+      break;
+    case GOP_CHECK_LOCAL_TRANSFERS:
+      phase_check_local_transfers (gorc);
+      break;
+    case GOP_REPLY_RESULT:
+      phase_reply_result (gorc);
+      break;
+    case GOP_ERROR:
+      phase_error (gorc);
+      break;
+    case GOP_SUSPENDED_ON_UNPAID:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Suspending order request awaiting payment\n");
       return MHD_YES;
-    }
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Order %s claimed but not paid yet\n",
-                hc->infix);
-    return TALER_MHD_REPLY_JSON_PACK (
-      connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_object_incref ("contract_terms",
-                                      gorc->contract_terms),
-      GNUNET_JSON_pack_string ("order_status",
-                               "claimed"));
-  }
-  if (paid &&
-      (! wired) &&
-      gorc->transfer_status_requested)
-  {
-    /* suspend connection, wait for exchange to check wire transfer status 
there */
-    gorc->transfer_status_requested = false;   /* only try ONCE */
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (gorc->contract_amount.currency,
-                                          &gorc->deposits_total));
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (gorc->contract_amount.currency,
-                                          &gorc->deposit_fees_total));
-    TMH_db->lookup_deposits_by_order (TMH_db->cls,
-                                      gorc->order_serial,
-                                      &deposit_cb,
-                                      gorc);
-    if (NULL != gorc->tq_head)
-    {
-      GNUNET_CONTAINER_DLL_insert (gorc_head,
-                                   gorc_tail,
-                                   gorc);
-      gorc->suspended = GNUNET_YES;
-      MHD_suspend_connection (connection);
-      gorc->tt = GNUNET_SCHEDULER_add_delayed (EXCHANGE_TIMEOUT,
-                                               &exchange_timeout_cb,
-                                               gorc);
+    case GOP_SUSPENDED_ON_EXCHANGE:
+      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                  "Suspending order request awaiting answer from exchange\n");
       return MHD_YES;
+    case GOP_END_YES:
+      return MHD_YES;
+    case GOP_END_NO:
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Closing connection, no response generated\n");
+      return MHD_NO;
     }
-  }
-
-  if ( (! paid) &&
-       (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout)) )
-  {
-    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-                "Suspending GET /private/orders/%s\n",
-                hc->infix);
-    GNUNET_assert (GNUNET_NO == gorc->suspended);
-    GNUNET_CONTAINER_DLL_insert (gorc_head,
-                                 gorc_tail,
-                                 gorc);
-    gorc->suspended = GNUNET_YES;
-    MHD_suspend_connection (gorc->sc.con);
-    return MHD_YES;
-  }
-
-  if (! paid)
-  {
-    /* User never paid for this order */
-    char *taler_pay_uri;
-    char *order_status_url;
-    MHD_RESULT ret;
-
-    taler_pay_uri = TMH_make_taler_pay_uri (connection,
-                                            hc->infix,
-                                            gorc->session_id,
-                                            hc->instance->settings.id,
-                                            &claim_token);
-    order_status_url = TMH_make_order_status_url (connection,
-                                                  hc->infix,
-                                                  gorc->session_id,
-                                                  hc->instance->settings.id,
-                                                  &claim_token,
-                                                  NULL);
-    ret = TALER_MHD_REPLY_JSON_PACK (
-      connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_string ("taler_pay_uri",
-                               taler_pay_uri),
-      GNUNET_JSON_pack_string ("order_status_url",
-                               order_status_url),
-      GNUNET_JSON_pack_string ("order_status",
-                               "unpaid"),
-      TALER_JSON_pack_amount ("total_amount",
-                              &gorc->contract_amount),
-      GNUNET_JSON_pack_string ("summary",
-                               summary),
-      GNUNET_JSON_pack_timestamp ("creation_time",
-                                  timestamp));
-    GNUNET_free (taler_pay_uri);
-    GNUNET_free (order_status_url);
-    return ret;
-  }
-
-  /* Here we know the user DID pay, compute refunds... */
-  GNUNET_assert (! order_only);
-  GNUNET_assert (paid);
-  /* Accumulate refunds, if any. */
-  {
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (gorc->contract_amount.currency,
-                                          &gorc->refund_amount));
-    qs = TMH_db->lookup_refunds_detailed (TMH_db->cls,
-                                          hc->instance->settings.id,
-                                          &gorc->h_contract_terms,
-                                          &process_refunds_cb,
-                                          gorc);
-  }
-  if (0 > qs)
-  {
-    GNUNET_break (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                       "detailed refunds");
-  }
-  if (gorc->refund_currency_mismatch)
-  {
-    GNUNET_break (0);
-    return TALER_MHD_reply_with_error (connection,
-                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                       "refunds in different currency than 
original order price");
-  }
-
-  /* Generate final reply, including wire details if we have them */
-  {
-    MHD_RESULT ret;
-    char *order_status_url;
-
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (gorc->contract_amount.currency,
-                                          &gorc->deposits_total));
-    GNUNET_assert (GNUNET_OK ==
-                   TALER_amount_set_zero (gorc->contract_amount.currency,
-                                          &gorc->deposit_fees_total));
-    qs = TMH_db->lookup_transfer_details_by_order (TMH_db->cls,
-                                                   gorc->order_serial,
-                                                   &process_transfer_details,
-                                                   gorc);
-    if (0 > qs)
-    {
-      GNUNET_break (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                         "transfer details");
-    }
-    if (gorc->deposit_currency_mismatch)
-    {
-      GNUNET_break (0);
-      return TALER_MHD_reply_with_error (connection,
-                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
-                                         "deposits in different currency than 
original order price");
-    }
-
-    if (! wired)
-    {
-      /* we believe(d) the wire transfer did not happen yet, check if maybe
-         in light of new evidence it did */
-      struct TALER_Amount expect_total;
-
-      if (0 >
-          TALER_amount_subtract (&expect_total,
-                                 &gorc->contract_amount,
-                                 &gorc->refund_amount))
-      {
-        GNUNET_break (0);
-        return TALER_MHD_reply_with_error (
-          connection,
-          MHD_HTTP_INTERNAL_SERVER_ERROR,
-          TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
-          "refund exceeds contract value");
-      }
-      if (0 >
-          TALER_amount_subtract (&expect_total,
-                                 &expect_total,
-                                 &gorc->deposit_fees_total))
-      {
-        GNUNET_break (0);
-        return TALER_MHD_reply_with_error (
-          connection,
-          MHD_HTTP_INTERNAL_SERVER_ERROR,
-          TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
-          "deposit fees exceed total minus refunds");
-      }
-      if (0 >=
-          TALER_amount_cmp (&expect_total,
-                            &gorc->deposits_total))
-      {
-        /* expect_total <= gorc->deposits_total: good: we got paid */
-        wired = true;
-        qs = TMH_db->mark_order_wired (TMH_db->cls,
-                                       gorc->order_serial);
-        GNUNET_break (qs >= 0); /* just warn if transaction failed */
-        TMH_notify_order_change (hc->instance,
-                                 TMH_OSF_PAID
-                                 | TMH_OSF_WIRED,
-                                 timestamp,
-                                 gorc->order_serial);
-      }
-    }
-
-    {
-      struct TALER_PrivateContractHashP *h_contract = NULL;
-
-      /* In a session-bound payment, allow the browser to check the order
-       * status page (e.g. to get a refund).
-       *
-       * Note that we don't allow this outside of session-based payment, as
-       * otherwise this becomes an oracle to convert order_id to h_contract.
-       */if (NULL != gorc->session_id)
-        h_contract = &gorc->h_contract_terms;
-
-      order_status_url =
-        TMH_make_order_status_url (connection,
-                                   hc->infix,
-                                   gorc->session_id,
-                                   hc->instance->settings.id,
-                                   &claim_token,
-                                   h_contract);
-    }
-
-    ret = TALER_MHD_REPLY_JSON_PACK (
-      connection,
-      MHD_HTTP_OK,
-      GNUNET_JSON_pack_array_steal ("wire_reports",
-                                    gorc->wire_reports),
-      GNUNET_JSON_pack_uint64 ("exchange_code",
-                               gorc->exchange_ec),
-      GNUNET_JSON_pack_uint64 ("exchange_http_status",
-                               gorc->exchange_hc),
-      /* legacy: */
-      GNUNET_JSON_pack_uint64 ("exchange_ec",
-                               gorc->exchange_ec),
-      /* legacy: */
-      GNUNET_JSON_pack_uint64 ("exchange_hc",
-                               gorc->exchange_hc),
-      TALER_JSON_pack_amount ("deposit_total",
-                              &gorc->deposits_total),
-      GNUNET_JSON_pack_object_incref ("contract_terms",
-                                      gorc->contract_terms),
-      GNUNET_JSON_pack_string ("order_status",
-                               "paid"),
-      GNUNET_JSON_pack_bool ("refunded",
-                             gorc->refunded),
-      GNUNET_JSON_pack_bool ("wired",
-                             wired),
-      GNUNET_JSON_pack_bool ("refund_pending",
-                             gorc->refund_pending),
-      TALER_JSON_pack_amount ("refund_amount",
-                              &gorc->refund_amount),
-      GNUNET_JSON_pack_array_steal ("wire_details",
-                                    gorc->wire_details),
-      GNUNET_JSON_pack_array_steal ("refund_details",
-                                    gorc->refund_details),
-      GNUNET_JSON_pack_string ("order_status_url",
-                               order_status_url));
-    GNUNET_free (order_status_url);
-    gorc->wire_details = NULL;
-    gorc->wire_reports = NULL;
-    gorc->refund_details = NULL;
-    return ret;
-  }
+  } /* end first-time per-request initialization */
 }

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