/*
  This file is part of TALER
  (C) 2014-2024 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU Affero General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file taler-merchant-httpd_get-orders-ID.c
 * @brief implementation of GET /orders/$ID
 * @author Marcello Stanisci
 * @author Christian Grothoff
 */
#include "platform.h"
#include <jansson.h>
#include <gnunet/gnunet_uri_lib.h>
#include <gnunet/gnunet_common.h>
#include <taler/taler_signatures.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_templating_lib.h>
#include <taler/taler_exchange_service.h>
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_get-orders-ID.h"
#include "taler-merchant-httpd_mhd.h"
#include "taler-merchant-httpd_qr.h"
#include "taler/taler_error_codes.h"
#include "taler/taler_util.h"
#include "taler_merchant_util.h"

/**
 * How often do we retry DB transactions on serialization failures?
 */
#define MAX_RETRIES 5


/**
 * The different phases in which we handle the request.
 */
enum Phase
{
  GOP_INIT = 0,
  GOP_LOOKUP_TERMS = 1,
  GOP_PARSE_CONTRACT = 2,
  GOP_CHECK_CLIENT_ACCESS = 3,
  GOP_CHECK_PAID = 4,
  GOP_REDIRECT_TO_PAID_ORDER = 5,
  GOP_HANDLE_UNPAID = 6,
  GOP_CHECK_REFUNDED = 7,
  GOP_RETURN_STATUS = 8,
  GOP_RETURN_MHD_YES = 9,
  GOP_RETURN_MHD_NO = 10
};


/**
 * Context for the operation.
 */
struct GetOrderData
{

  /**
   * Hashed version of contract terms. All zeros if not provided.
   */
  struct TALER_PrivateContractHashP h_contract_terms;

  /**
   * Claim token used for access control. All zeros if not provided.
   */
  struct TALER_ClaimTokenP claim_token;

  /**
   * DLL of (suspended) requests.
   */
  struct GetOrderData *next;

  /**
   * DLL of (suspended) requests.
   */
  struct GetOrderData *prev;

  /**
   * Context of the request.
   */
  struct TMH_HandlerContext *hc;

  /**
   * Entry in the #resume_timeout_heap for this check payment, if we are
   * suspended.
   */
  struct TMH_SuspendedConnection sc;

  /**
   * Database event we are waiting on to be resuming on payment.
   */
  struct GNUNET_DB_EventHandler *pay_eh;

  /**
   * Database event we are waiting on to be resuming for refunds.
   */
  struct GNUNET_DB_EventHandler *refund_eh;

  /**
   * Database event we are waiting on to be resuming for repurchase
   * detection updating some equivalent order (same fulfillment URL)
   * to our session.
   */
  struct GNUNET_DB_EventHandler *session_eh;

  /**
   * Which merchant instance is this for?
   */
  struct MerchantInstance *mi;

  /**
   * order ID for the payment
   */
  const char *order_id;

  /**
   * session of the client
   */
  const char *session_id;

  /**
   * choice index (contract v1)
   */
  int16_t choice_index;

  /**
   * Contract terms of the payment we are checking. NULL when they
   * are not (yet) known.
   */
  json_t *contract_terms_json;

  /**
   * Parsed contract terms, NULL when parsing failed.
   */
  struct TALER_MERCHANT_Contract *contract_terms;

  /**
   * Total refunds granted for this payment. Only initialized
   * if @e refunded is set to true.
   */
  struct TALER_Amount refund_amount;

  /**
   * Total refunds already collected.
   * if @e refunded is set to true.
   */
  struct TALER_Amount refund_taken;

  /**
   * Phase in which we currently are handling this
   * request.
   */
  enum Phase phase;

  /**
   * Return code: #TALER_EC_NONE if successful.
   */
  enum TALER_ErrorCode ec;

  /**
   * Did we suspend @a connection and are thus in
   * the #god_head DLL (#GNUNET_YES). Set to
   * #GNUNET_NO if we are not suspended, and to
   * #GNUNET_SYSERR if we should close the connection
   * without a response due to shutdown.
   */
  enum GNUNET_GenericReturnValue suspended;

  /**
   * Set to YES if refunded orders should be included when
   * doing repurchase detection.
   */
  enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase;

  /**
   * Set to true if the client passed 'h_contract'.
   */
  bool h_contract_provided;

  /**
   * Set to true if the client passed a 'claim' token.
   */
  bool claim_token_provided;

  /**
   * Set to true if we are dealing with a claimed order
   * (and thus @e h_contract_terms is set, otherwise certain
   * DB queries will not work).
   */
  bool claimed;

  /**
   * Set to true if this order was paid.
   */
  bool paid;

  /**
   * Set to true if this order has been refunded and
   * @e refund_amount is initialized.
   */
  bool refunded;

  /**
   * Set to true if a refund is still available for the
   * wallet for this payment.
   * @deprecated: true if refund_taken < refund_amount
   */
  bool refund_pending;

  /**
   * Set to true if the client requested HTML, otherwise we generate JSON.
   */
  bool generate_html;

  /**
   * Did we parse the contract terms?
   */
  bool contract_parsed;

  /**
   * Set to true if the refunds found in the DB have
   * a different currency then the main contract.
   */
  bool bad_refund_currency_in_db;

  /**
   * Did the hash of the contract match the contract
   * hash supplied by the client?
   */
  bool contract_match;

  /**
   * True if we had a claim token and the claim token
   * provided by the client matched our claim token.
   */
  bool token_match;

  /**
   * True if we found a (claimed) contract for the order,
   * false if we had an unclaimed order.
   */
  bool contract_available;

};


/**
 * Head of DLL of (suspended) requests.
 */
static struct GetOrderData *god_head;

/**
 * Tail of DLL of (suspended) requests.
 */
static struct GetOrderData *god_tail;


void
TMH_force_wallet_get_order_resume (void)
{
  struct GetOrderData *god;

  while (NULL != (god = god_head))
  {
    GNUNET_CONTAINER_DLL_remove (god_head,
                                 god_tail,
                                 god);
    GNUNET_assert (god->suspended);
    god->suspended = GNUNET_SYSERR;
    MHD_resume_connection (god->sc.con);
    TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
  }
}


/**
 * Suspend this @a god until the trigger is satisfied.
 *
 * @param god request to suspend
 */
static void
suspend_god (struct GetOrderData *god)
{
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Suspending GET /orders/%s\n",
              god->order_id);
  /* We reset the contract terms and start by looking them up
     again, as while we are suspended fundamental things could
     change (such as the contract being claimed) */
  if (NULL != god->contract_terms_json)
  {
    json_decref (god->contract_terms_json);
    god->contract_terms_json = NULL;
    god->contract_parsed = false;
  }
  if (NULL != god->contract_terms)
  {
    TALER_MERCHANT_contract_free (god->contract_terms);
    god->contract_terms = NULL;
  }
  GNUNET_assert (! god->suspended);
  god->contract_parsed = false;
  god->contract_match = false;
  god->token_match = false;
  god->contract_available = false;
  god->phase = GOP_LOOKUP_TERMS;
  god->suspended = GNUNET_YES;
  GNUNET_CONTAINER_DLL_insert (god_head,
                               god_tail,
                               god);
  MHD_suspend_connection (god->sc.con);
}


/**
 * Clean up the session state for a GET /orders/$ID request.
 *
 * @param cls must be a `struct GetOrderData *`
 */
static void
god_cleanup (void *cls)
{
  struct GetOrderData *god = cls;

  if (NULL != god->contract_terms_json)
  {
    json_decref (god->contract_terms_json);
    god->contract_terms_json = NULL;
  }
  if (NULL != god->contract_terms)
  {
    TALER_MERCHANT_contract_free (god->contract_terms);
    god->contract_terms = NULL;
  }
  if (NULL != god->session_eh)
  {
    TMH_db->event_listen_cancel (god->session_eh);
    god->session_eh = NULL;
  }
  if (NULL != god->refund_eh)
  {
    TMH_db->event_listen_cancel (god->refund_eh);
    god->refund_eh = NULL;
  }
  if (NULL != god->pay_eh)
  {
    TMH_db->event_listen_cancel (god->pay_eh);
    god->pay_eh = NULL;
  }
  GNUNET_free (god);
}


/**
 * Finish the request by returning @a mret as the
 * final result.
 *
 * @param[in,out] god request we are processing
 * @param mret MHD result to return
 */
static void
phase_end (struct GetOrderData *god,
           MHD_RESULT mret)
{
  god->phase = (MHD_YES == mret)
    ? GOP_RETURN_MHD_YES
    : GOP_RETURN_MHD_NO;
}


/**
 * Finish the request by returning an error @a ec
 * with HTTP status @a http_status and @a message.
 *
 * @param[in,out] god request we are processing
 * @param http_status HTTP status code to return
 * @param ec error code to return
 * @param message human readable hint to return, can be NULL
 */
static void
phase_fail (struct GetOrderData *god,
            unsigned int http_status,
            enum TALER_ErrorCode ec,
            const char *message)
{
  phase_end (god,
             TALER_MHD_reply_with_error (god->sc.con,
                                         http_status,
                                         ec,
                                         message));
}


/**
 * We have received a trigger from the database
 * that we should (possibly) resume the request.
 *
 * @param cls a `struct GetOrderData` to resume
 * @param extra string encoding refund amount (or NULL)
 * @param extra_size number of bytes in @a extra
 */
static void
resume_by_event (void *cls,
                 const void *extra,
                 size_t extra_size)
{
  struct GetOrderData *god = cls;
  struct GNUNET_AsyncScopeSave old;

  GNUNET_async_scope_enter (&god->hc->async_scope_id,
                            &old);
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Received event for %s with argument `%.*s`\n",
              god->order_id,
              (int) extra_size,
              (const char *) extra);
  if (! god->suspended)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Not suspended, ignoring event\n");
    GNUNET_async_scope_restore (&old);
    return; /* duplicate event is possible */
  }
  if (GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout) &&
      god->sc.awaiting_refund)
  {
    char *as;
    struct TALER_Amount a;

    if (0 == extra_size)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "No amount given, but need refund above threshold\n");
      GNUNET_async_scope_restore (&old);
      return; /* not relevant */
    }
    as = GNUNET_strndup (extra,
                         extra_size);
    if (GNUNET_OK !=
        TALER_string_to_amount (as,
                                &a))
    {
      GNUNET_break (0);
      GNUNET_async_scope_restore (&old);
      GNUNET_free (as);
      return;
    }
    GNUNET_free (as);
    if (GNUNET_OK !=
        TALER_amount_cmp_currency (&god->sc.refund_expected,
                                   &a))
    {
      GNUNET_break (0);
      GNUNET_async_scope_restore (&old);
      return; /* bad currency!? */
    }
    if (1 == TALER_amount_cmp (&god->sc.refund_expected,
                               &a))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Amount too small to trigger resuming\n");
      GNUNET_async_scope_restore (&old);
      return; /* refund too small */
    }
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Resuming (%s/%s) by event with argument `%.*s`\n",
              GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout)
              ? "future"
              : "past",
              god->sc.awaiting_refund
              ? "awaiting refund"
              : "not waiting for refund",
              (int) extra_size,
              (const char *) extra);
  god->suspended = GNUNET_NO;
  GNUNET_CONTAINER_DLL_remove (god_head,
                               god_tail,
                               god);
  MHD_resume_connection (god->sc.con);
  TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
  GNUNET_async_scope_restore (&old);
}


/**
 * First phase (after request parsing).
 * Set up long-polling.
 *
 * @param[in,out] god request context
 */
static void
phase_init (struct GetOrderData *god)
{
  god->phase++;
  if (god->generate_html)
    return; /* If HTML is requested, we never actually long poll. */
  if (! GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout))
    return; /* long polling not requested */

  if (god->sc.awaiting_refund ||
      god->sc.awaiting_refund_obtained)
  {
    struct TMH_OrderPayEventP refund_eh = {
      .header.size = htons (sizeof (refund_eh)),
      .header.type = htons (god->sc.awaiting_refund_obtained
                                    ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED
                                    : TALER_DBEVENT_MERCHANT_ORDER_REFUND),
      .merchant_pub = god->hc->instance->merchant_pub
    };

    GNUNET_CRYPTO_hash (god->order_id,
                        strlen (god->order_id),
                        &refund_eh.h_order_id);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Subscribing %p to refunds on %s\n",
                god,
                god->order_id);
    god->refund_eh
      = TMH_db->event_listen (
          TMH_db->cls,
          &refund_eh.header,
          GNUNET_TIME_absolute_get_remaining (
            god->sc.long_poll_timeout),
          &resume_by_event,
          god);
  }
  {
    struct TMH_OrderPayEventP pay_eh = {
      .header.size = htons (sizeof (pay_eh)),
      .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
      .merchant_pub = god->hc->instance->merchant_pub
    };

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Subscribing to payments on %s\n",
                god->order_id);
    GNUNET_CRYPTO_hash (god->order_id,
                        strlen (god->order_id),
                        &pay_eh.h_order_id);
    god->pay_eh
      = TMH_db->event_listen (
          TMH_db->cls,
          &pay_eh.header,
          GNUNET_TIME_absolute_get_remaining (
            god->sc.long_poll_timeout),
          &resume_by_event,
          god);
  }
}


/**
 * Lookup contract terms and check client has the
 * right to access this order (by claim token or
 * contract hash).
 *
 * @param[in,out] god request context
 */
static void
phase_lookup_terms (struct GetOrderData *god)
{
  uint64_t order_serial;
  struct TALER_ClaimTokenP db_claim_token;

  /* Convert order_id to h_contract_terms */
  TMH_db->preflight (TMH_db->cls);
  GNUNET_assert (NULL == god->contract_terms_json);

  {
    enum GNUNET_DB_QueryStatus qs;

    bool paid;
    bool wired;
    bool session_matches;
    qs = TMH_db->lookup_contract_terms3 (
      TMH_db->cls,
      god->hc->instance->settings.id,
      god->order_id,
      NULL,
      &god->contract_terms_json,
      &order_serial,
      &paid,
      &wired,
      &session_matches,
      &db_claim_token,
      &god->choice_index);
    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 (0);
      phase_fail (god,
                  MHD_HTTP_INTERNAL_SERVER_ERROR,
                  TALER_EC_GENERIC_DB_FETCH_FAILED,
                  "lookup_contract_terms");
      return;
    }
    /* Note: when "!ord.requireClaimToken" and the client does not provide
       a claim token (all zeros!), then token_match==TRUE below: */
    god->token_match
      = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
        && (0 == GNUNET_memcmp (&db_claim_token,
                                &god->claim_token));
  }

  /* Check if client provided the right hash code of the contract terms */
  if (NULL != god->contract_terms_json)
  {
    god->contract_available = true;
    if (GNUNET_YES ==
        GNUNET_is_zero (&god->h_contract_terms))
    {
      if (GNUNET_OK !=
          TALER_JSON_contract_hash (god->contract_terms_json,
                                    &god->h_contract_terms))
      {
        GNUNET_break (0);
        phase_fail (god,
                    MHD_HTTP_INTERNAL_SERVER_ERROR,
                    TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
                    "contract terms");
        return;
      }
    }
    else
    {
      struct TALER_PrivateContractHashP h;

      if (GNUNET_OK !=
          TALER_JSON_contract_hash (god->contract_terms_json,
                                    &h))
      {
        GNUNET_break (0);
        phase_fail (god,
                    MHD_HTTP_INTERNAL_SERVER_ERROR,
                    TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
                    "contract terms");
        return;
      }
      god->contract_match = (0 ==
                             GNUNET_memcmp (&h,
                                            &god->h_contract_terms));
      if (! god->contract_match)
      {
        GNUNET_break_op (0);
        phase_fail (god,
                    MHD_HTTP_FORBIDDEN,
                    TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
                    NULL);
        return;
      }
    }
  }

  if (god->contract_available)
  {
    god->claimed = true;
  }
  else
  {
    struct TALER_MerchantPostDataHashP unused;
    enum GNUNET_DB_QueryStatus qs;

    qs = TMH_db->lookup_order (
      TMH_db->cls,
      god->hc->instance->settings.id,
      god->order_id,
      &db_claim_token,
      &unused,
      (NULL == god->contract_terms_json)
      ? &god->contract_terms_json
      : NULL);
    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 (0);
      phase_fail (god,
                  MHD_HTTP_INTERNAL_SERVER_ERROR,
                  TALER_EC_GENERIC_DB_FETCH_FAILED,
                  "lookup_order");
      return;
    }
    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Unknown order id given: `%s'\n",
                  god->order_id);
      phase_fail (god,
                  MHD_HTTP_NOT_FOUND,
                  TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
                  god->order_id);
      return;
    }
    /* Note: when "!ord.requireClaimToken" and the client does not provide
       a claim token (all zeros!), then token_match==TRUE below: */
    god->token_match
      = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) &&
        (0 == GNUNET_memcmp (&db_claim_token,
                             &god->claim_token));
  } /* end unclaimed order logic */
  god->phase++;
}


/**
 * Parse contract terms.
 *
 * @param[in,out] god request context
 */
static void
phase_parse_contract (struct GetOrderData *god)
{
  GNUNET_break (NULL == god->contract_terms);
  god->contract_terms = TALER_MERCHANT_contract_parse (
    god->contract_terms_json,
    true);

  if (NULL == god->contract_terms)
  {
    phase_fail (god,
                MHD_HTTP_INTERNAL_SERVER_ERROR,
                TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
                god->order_id);
    return;
  }
  god->contract_parsed = true;
  if ( (NULL != god->session_id) &&
       (NULL != god->contract_terms->fulfillment_url) &&
       (NULL == god->session_eh) )
  {
    struct TMH_SessionEventP session_eh = {
      .header.size = htons (sizeof (session_eh)),
      .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
      .merchant_pub = god->hc->instance->merchant_pub
    };

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Subscribing to session triggers for %p\n",
                god);
    GNUNET_CRYPTO_hash (god->session_id,
                        strlen (god->session_id),
                        &session_eh.h_session_id);
    GNUNET_CRYPTO_hash (god->contract_terms->fulfillment_url,
                        strlen (god->contract_terms->fulfillment_url),
                        &session_eh.h_fulfillment_url);
    god->session_eh
      = TMH_db->event_listen (
          TMH_db->cls,
          &session_eh.header,
          GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout),
          &resume_by_event,
          god);
  }
  god->phase++;
}


/**
 * Check that this order is unclaimed or claimed by
 * this client.
 *
 * @param[in,out] god request context
 */
static void
phase_check_client_access (struct GetOrderData *god)
{
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Token match: %d, contract_available: %d, contract match: %d, claimed: %d\n",
              god->token_match,
              god->contract_available,
              god->contract_match,
              god->claimed);

  if (god->claim_token_provided && ! god->token_match)
  {
    /* Authentication provided but wrong. */
    GNUNET_break_op (0);
    phase_fail (god,
                MHD_HTTP_FORBIDDEN,
                TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
                "authentication with claim token provided but wrong");
    return;
  }

  if (god->h_contract_provided && ! god->contract_match)
  {
    /* Authentication provided but wrong. */
    GNUNET_break_op (0);
    phase_fail (god,
                MHD_HTTP_FORBIDDEN,
                TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH,
                NULL);
    return;
  }

  if (! (god->token_match ||
         god->contract_match) )
  {

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Neither claim token nor contract matched\n");
    /* Client has no rights to this order */
    if (NULL == god->contract_terms->public_reorder_url)
    {
      /* We cannot give the client a new order, just fail */
      if (! GNUNET_is_zero (&god->h_contract_terms))
      {
        GNUNET_break_op (0);
        phase_fail (god,
                    MHD_HTTP_FORBIDDEN,
                    TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER,
                    NULL);
        return;
      }
      GNUNET_break_op (0);
      phase_fail (god,
                  MHD_HTTP_FORBIDDEN,
                  TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN,
                  "no 'public_reorder_url'");
      return;
    }
    /* We have a fulfillment URL, redirect the client there, maybe
       the frontend can generate a fresh order for this new customer */
    if (god->generate_html)
    {
      /* Contract was claimed (maybe by another device), so this client
         cannot get the status information. Redirect to fulfillment page,
         where the client may be able to pickup a fresh order -- or might
         be able authenticate via session ID */
      struct MHD_Response *reply;
      MHD_RESULT ret;

      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Contract claimed, redirecting to fulfillment page for order %s\n",
                  god->order_id);
      reply = MHD_create_response_from_buffer (0,
                                               NULL,
                                               MHD_RESPMEM_PERSISTENT);
      if (NULL == reply)
      {
        GNUNET_break (0);
        phase_end (god,
                   MHD_NO);
        return;
      }
      GNUNET_break (MHD_YES ==
                    MHD_add_response_header (
                      reply,
                      MHD_HTTP_HEADER_LOCATION,
                      god->contract_terms->public_reorder_url));
      ret = MHD_queue_response (god->sc.con,
                                MHD_HTTP_FOUND,
                                reply);
      MHD_destroy_response (reply);
      phase_end (god,
                 ret);
      return;
    }
    /* Need to generate JSON reply */
    phase_end (god,
               TALER_MHD_REPLY_JSON_PACK (
                 god->sc.con,
                 MHD_HTTP_ACCEPTED,
                 GNUNET_JSON_pack_string (
                   "public_reorder_url",
                   god->contract_terms->public_reorder_url)));
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Claim token or contract matched\n");
  god->phase++;
}


/**
 * Return the order summary of the contract of @a god in the
 * preferred language of the HTTP client.
 *
 * @param god order to extract summary from
 * @return dummy error message summary if no summary was provided in the contract
 */
static const char *
get_order_summary (const struct GetOrderData *god)
{
  const char *language_pattern;
  const char *ret;

  language_pattern = MHD_lookup_connection_value (god->sc.con,
                                                  MHD_HEADER_KIND,
                                                  MHD_HTTP_HEADER_ACCEPT_LANGUAGE);
  if (NULL == language_pattern)
    language_pattern = "en";
  ret = json_string_value (TALER_JSON_extract_i18n (god->contract_terms_json,
                                                    language_pattern,
                                                    "summary"));
  if (NULL == ret)
  {
    /* Upon order creation (and insertion into the database), the presence
       of a summary should have been checked. So if we get here, someone
       did something fishy to our database... */
    GNUNET_break (0);
    ret = "<bug: no summary>";
  }
  return ret;
}


/**
 * The client did not yet pay, send it the payment request.
 *
 * @param god check pay request context
 * @param already_paid_order_id if for the fulfillment URI there is
 *          already a paid order, this is the order ID to redirect
 *          the wallet to; NULL if not applicable
 * @return true to exit due to suspension
 */
static bool
send_pay_request (struct GetOrderData *god,
                  const char *already_paid_order_id)
{
  MHD_RESULT ret;
  char *taler_pay_uri;
  char *order_status_url;
  struct GNUNET_TIME_Relative remaining;

  remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
  if ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
       (NULL == already_paid_order_id) )
  {
    /* long polling: do not queue a response, suspend connection instead */
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Suspending request: long polling for payment\n");
    suspend_god (god);
    return true;
  }

  /* Check if resource_id has been paid for in the same session
   * with another order_id.
   */
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Sending payment request\n");
  taler_pay_uri = TMH_make_taler_pay_uri (
    god->sc.con,
    god->order_id,
    god->session_id,
    god->hc->instance->settings.id,
    &god->claim_token);
  order_status_url = TMH_make_order_status_url (
    god->sc.con,
    god->order_id,
    god->session_id,
    god->hc->instance->settings.id,
    &god->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_fail (god,
                MHD_HTTP_BAD_REQUEST,
                TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
                "host");
    return false;
  }
  if (god->generate_html)
  {
    if (NULL != already_paid_order_id)
    {
      struct MHD_Response *reply;

      GNUNET_assert (NULL != god->contract_terms->fulfillment_url);
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Redirecting to already paid order %s via fulfillment URL %s\n",
                  already_paid_order_id,
                  god->contract_terms->fulfillment_url);
      reply = MHD_create_response_from_buffer (0,
                                               NULL,
                                               MHD_RESPMEM_PERSISTENT);
      if (NULL == reply)
      {
        GNUNET_break (0);
        phase_end (god,
                   MHD_NO);
        return false;
      }
      GNUNET_break (MHD_YES ==
                    MHD_add_response_header (
                      reply,
                      MHD_HTTP_HEADER_LOCATION,
                      god->contract_terms->fulfillment_url));
      {
        ret = MHD_queue_response (god->sc.con,
                                  MHD_HTTP_FOUND,
                                  reply);
        MHD_destroy_response (reply);
        phase_end (god,
                   ret);
        return false;
      }
    }

    {
      char *qr;

      qr = TMH_create_qrcode (taler_pay_uri);
      if (NULL == qr)
      {
        GNUNET_break (0);
        phase_end (god,
                   MHD_NO);
        return false;
      }
      {
        enum GNUNET_GenericReturnValue res;
        json_t *context;

        context = GNUNET_JSON_PACK (
          GNUNET_JSON_pack_string ("taler_pay_uri",
                                   taler_pay_uri),
          GNUNET_JSON_pack_string ("order_status_url",
                                   order_status_url),
          GNUNET_JSON_pack_string ("taler_pay_qrcode_svg",
                                   qr),
          GNUNET_JSON_pack_string ("order_summary",
                                   get_order_summary (god)));
        res = TALER_TEMPLATING_reply (
          god->sc.con,
          MHD_HTTP_PAYMENT_REQUIRED,
          "request_payment",
          god->hc->instance->settings.id,
          taler_pay_uri,
          context);
        if (GNUNET_SYSERR == res)
        {
          GNUNET_break (0);
          ret = MHD_NO;
        }
        else
        {
          ret = MHD_YES;
        }
        json_decref (context);
      }
      GNUNET_free (qr);
    }
  }
  else /* end of 'generate HTML' */
  {
    ret = TALER_MHD_REPLY_JSON_PACK (
      god->sc.con,
      MHD_HTTP_PAYMENT_REQUIRED,
      GNUNET_JSON_pack_string ("taler_pay_uri",
                               taler_pay_uri),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_string ("fulfillment_url",
                                 god->contract_terms->fulfillment_url)),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_string ("already_paid_order_id",
                                 already_paid_order_id)));
  }
  GNUNET_free (taler_pay_uri);
  GNUNET_free (order_status_url);
  phase_end (god,
             ret);
  return false;
}


/**
 * Check if the order has been paid.
 *
 * @param[in,out] god request context
 */
static void
phase_check_paid (struct GetOrderData *god)
{
  enum GNUNET_DB_QueryStatus qs;
  struct TALER_PrivateContractHashP h_contract;

  god->paid = false;
  qs = TMH_db->lookup_order_status (
    TMH_db->cls,
    god->hc->instance->settings.id,
    god->order_id,
    &h_contract,
    &god->paid);
  if (0 > qs)
  {
    /* Always report on hard error as well to enable diagnostics */
    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    phase_fail (god,
                MHD_HTTP_INTERNAL_SERVER_ERROR,
                TALER_EC_GENERIC_DB_FETCH_FAILED,
                "lookup_order_status");
    return;
  }
  god->phase++;
}


/**
 * Check if the client already paid for an equivalent
 * order under this session, and if so redirect to
 * that order.
 *
 * @param[in,out] god request context
 * @return true to exit due to suspension
 */
static bool
phase_redirect_to_paid_order (struct GetOrderData *god)
{
  if ( (NULL != god->session_id) &&
       (NULL != god->contract_terms->fulfillment_url) )
  {
    /* Check if client paid for this fulfillment article
       already within this session, but using a different
       order ID. If so, redirect the client to the order
       it already paid.  Allows, for example, the case
       where a mobile phone pays for a browser's session,
       where the mobile phone has a different order
       ID (because it purchased the article earlier)
       than the one that the browser is waiting for. */
    char *already_paid_order_id = NULL;
    enum GNUNET_DB_QueryStatus qs;

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Running re-purchase detection for %s/%s\n",
                god->session_id,
                god->contract_terms->fulfillment_url);
    qs = TMH_db->lookup_order_by_fulfillment (
      TMH_db->cls,
      god->hc->instance->settings.id,
      god->contract_terms->fulfillment_url,
      god->session_id,
      TALER_EXCHANGE_YNA_NO != god->allow_refunded_for_repurchase,
      &already_paid_order_id);
    if (qs < 0)
    {
      /* 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_fail (god,
                  MHD_HTTP_INTERNAL_SERVER_ERROR,
                  TALER_EC_GENERIC_DB_FETCH_FAILED,
                  "order by fulfillment");
      return false;
    }
    if ( (! god->paid) &&
         ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
           (0 != strcmp (god->order_id,
                         already_paid_order_id)) ) )
    {
      bool ret;

      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Sending pay request for order %s (already paid: %s)\n",
                  god->order_id,
                  already_paid_order_id);
      ret = send_pay_request (god,
                              already_paid_order_id);
      GNUNET_free (already_paid_order_id);
      return ret;
    }
    GNUNET_free (already_paid_order_id);
  }
  god->phase++;
  return false;
}


/**
 * Check if the order has been paid, and if not
 * request payment.
 *
 * @param[in,out] god request context
 * @return true to exit due to suspension
 */
static bool
phase_handle_unpaid (struct GetOrderData *god)
{
  if (god->paid)
  {
    god->phase++;
    return false;
  }
  if (god->claimed)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Order claimed but unpaid, sending pay request for order %s\n",
                god->order_id);
  }
  else
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Order unclaimed, sending pay request for order %s\n",
                god->order_id);
  }
  return send_pay_request (god,
                           NULL);
}


/**
 * Function called with detailed information about a refund.
 * It is responsible for packing up the data to return.
 *
 * @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 GetOrderData *god = cls;

  (void) refund_serial;
  (void) timestamp;
  (void) exchange_url;
  (void) rtransaction_id;
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Found refund of %s for coin %s with reason `%s' in database\n",
              TALER_amount2s (refund_amount),
              TALER_B2S (coin_pub),
              reason);
  god->refund_pending |= pending;
  if ( (GNUNET_OK !=
        TALER_amount_cmp_currency (&god->refund_taken,
                                   refund_amount)) ||
       (GNUNET_OK !=
        TALER_amount_cmp_currency (&god->refund_amount,
                                   refund_amount)) )
  {
    god->bad_refund_currency_in_db = true;
    return;
  }
  if (! pending)
  {
    GNUNET_assert (0 <=
                   TALER_amount_add (&god->refund_taken,
                                     &god->refund_taken,
                                     refund_amount));
  }
  GNUNET_assert (0 <=
                 TALER_amount_add (&god->refund_amount,
                                   &god->refund_amount,
                                   refund_amount));
  god->refunded = true;
}


/**
 * Check if the order has been refunded.
 *
 * @param[in,out] god request context
 * @return true to exit due to suspension
 */
static bool
phase_check_refunded (struct GetOrderData *god)
{
  enum GNUNET_DB_QueryStatus qs;
  struct TALER_Amount refund_amount;
  const char *refund_currency;

  switch (god->contract_terms->version)
  {
  case TALER_MERCHANT_CONTRACT_VERSION_0:
    refund_amount = god->contract_terms->details.v0.brutto;
    refund_currency = god->contract_terms->details.v0.brutto.currency;
    break;
  case TALER_MERCHANT_CONTRACT_VERSION_1:
    if (god->choice_index < 0)
    {
      // order was not paid, no refund to be checked
      god->phase++;
      return false;
    }
    GNUNET_assert (god->choice_index <
                   god->contract_terms->details.v1.choices_len);
    refund_currency = god->contract_terms->details.v1.choices[god->choice_index]
                      .amount.currency;
    GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (refund_currency,
                                                       &refund_amount));
    break;
  default:
    {
      GNUNET_break (0);
      phase_fail (god,
                  MHD_HTTP_INTERNAL_SERVER_ERROR,
                  TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION,
                  NULL);
      return false;
    }
  }

  if ( (god->sc.awaiting_refund) &&
       (GNUNET_OK !=
        TALER_amount_cmp_currency (&refund_amount,
                                   &god->sc.refund_expected)) )
  {
    GNUNET_break (0);
    phase_fail (god,
                MHD_HTTP_CONFLICT,
                TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
                refund_currency);
    return false;
  }

  /* At this point, we know the contract was paid. Let's check for
     refunds. First, clear away refunds found from previous invocations. */
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (refund_currency,
                                        &god->refund_amount));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (refund_currency,
                                        &god->refund_taken));
  qs = TMH_db->lookup_refunds_detailed (
    TMH_db->cls,
    god->hc->instance->settings.id,
    &god->h_contract_terms,
    &process_refunds_cb,
    god);
  if (0 > qs)
  {
    GNUNET_break (0);
    phase_fail (god,
                MHD_HTTP_INTERNAL_SERVER_ERROR,
                TALER_EC_GENERIC_DB_FETCH_FAILED,
                "lookup_refunds_detailed");
    return false;
  }
  if (god->bad_refund_currency_in_db)
  {
    GNUNET_break (0);
    phase_fail (god,
                MHD_HTTP_INTERNAL_SERVER_ERROR,
                TALER_EC_GENERIC_DB_FETCH_FAILED,
                "currency mix-up between contract price and refunds in database");
    return false;
  }
  if ( ((god->sc.awaiting_refund) &&
        ( (! god->refunded) ||
          (1 != TALER_amount_cmp (&god->refund_amount,
                                  &god->sc.refund_expected)) )) ||
       ( (god->sc.awaiting_refund_obtained) &&
         (god->refund_pending) ) )
  {
    /* Client is waiting for a refund larger than what we have, suspend
       until timeout */
    struct GNUNET_TIME_Relative remaining;

    remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout);
    if ( (! GNUNET_TIME_relative_is_zero (remaining)) &&
         (! god->generate_html) )
    {
      /* yes, indeed suspend */
      if (god->sc.awaiting_refund)
        GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                    "Awaiting refund exceeding %s\n",
                    TALER_amount2s (&god->sc.refund_expected));
      if (god->sc.awaiting_refund_obtained)
        GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                    "Awaiting pending refunds\n");
      suspend_god (god);
      return true;
    }
  }
  god->phase++;
  return false;
}


/**
 * Create a taler://refund/ URI for the given @a con and @a order_id
 * and @a instance_id.
 *
 * @param merchant_base_url URL to take host and path from;
 *        we cannot take it from the MHD connection as a browser
 *        may have changed 'http' to 'https' and we MUST be consistent
 *        with what the merchant's frontend used initially
 * @param order_id the order id
 * @return corresponding taler://refund/ URI, or NULL on missing "host"
 */
static char *
make_taler_refund_uri (const char *merchant_base_url,
                       const char *order_id)
{
  struct GNUNET_Buffer buf = { 0 };
  char *url;
  struct GNUNET_Uri uri;

  url = GNUNET_strdup (merchant_base_url);
  if (-1 == GNUNET_uri_parse (&uri,
                              url))
  {
    GNUNET_break (0);
    GNUNET_free (url);
    return NULL;
  }
  GNUNET_assert (NULL != order_id);
  GNUNET_buffer_write_str (&buf,
                           "taler");
  if (0 == strcasecmp ("http",
                       uri.scheme))
    GNUNET_buffer_write_str (&buf,
                             "+http");
  GNUNET_buffer_write_str (&buf,
                           "://refund/");
  GNUNET_buffer_write_str (&buf,
                           uri.host);
  if (0 != uri.port)
    GNUNET_buffer_write_fstr (&buf,
                              ":%u",
                              (unsigned int) uri.port);
  if (NULL != uri.path)
    GNUNET_buffer_write_path (&buf,
                              uri.path);
  GNUNET_buffer_write_path (&buf,
                            order_id);
  GNUNET_buffer_write_path (&buf,
                            ""); // Trailing slash
  GNUNET_free (url);
  return GNUNET_buffer_reap_str (&buf);
}


/**
 * Generate the order status response.
 *
 * @param[in,out] god request context
 */
static void
phase_return_status (struct GetOrderData *god)
{
  /* All operations done, build final response */
  if (! god->generate_html)
  {
    phase_end (god,
               TALER_MHD_REPLY_JSON_PACK (
                 god->sc.con,
                 MHD_HTTP_OK,
                 GNUNET_JSON_pack_allow_null (
                   GNUNET_JSON_pack_string ("fulfillment_url",
                                            god->contract_terms->fulfillment_url
                                            )),
                 GNUNET_JSON_pack_bool ("refunded",
                                        god->refunded),
                 GNUNET_JSON_pack_bool ("refund_pending",
                                        god->refund_pending),
                 TALER_JSON_pack_amount ("refund_taken",
                                         &god->refund_taken),
                 TALER_JSON_pack_amount ("refund_amount",
                                         &god->refund_amount)));
    return;
  }

  if (god->refund_pending)
  {
    char *qr;
    char *uri;

    GNUNET_assert (NULL != god->contract_terms_json);
    uri = make_taler_refund_uri (god->contract_terms->merchant_base_url,
                                 god->order_id);
    if (NULL == uri)
    {
      GNUNET_break (0);
      phase_fail (god,
                  MHD_HTTP_INTERNAL_SERVER_ERROR,
                  TALER_EC_GENERIC_ALLOCATION_FAILURE,
                  "refund URI");
      return;
    }
    qr = TMH_create_qrcode (uri);
    if (NULL == qr)
    {
      GNUNET_break (0);
      GNUNET_free (uri);
      phase_fail (god,
                  MHD_HTTP_INTERNAL_SERVER_ERROR,
                  TALER_EC_GENERIC_ALLOCATION_FAILURE,
                  "qr code");
      return;
    }

    {
      enum GNUNET_GenericReturnValue res;
      json_t *context;

      context = GNUNET_JSON_PACK (
        GNUNET_JSON_pack_string ("order_summary",
                                 get_order_summary (god)),
        TALER_JSON_pack_amount ("refund_amount",
                                &god->refund_amount),
        TALER_JSON_pack_amount ("refund_taken",
                                &god->refund_taken),
        GNUNET_JSON_pack_string ("taler_refund_uri",
                                 uri),
        GNUNET_JSON_pack_string ("taler_refund_qrcode_svg",
                                 qr));
      res = TALER_TEMPLATING_reply (
        god->sc.con,
        MHD_HTTP_OK,
        "offer_refund",
        god->hc->instance->settings.id,
        uri,
        context);
      GNUNET_break (GNUNET_OK == res);
      json_decref (context);
      phase_end (god,
                 (GNUNET_SYSERR == res)
                 ? MHD_NO
                 : MHD_YES);
    }
    GNUNET_free (uri);
    GNUNET_free (qr);
    return;
  }

  {
    enum GNUNET_GenericReturnValue res;
    json_t *context;

    context = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_object_incref ("contract_terms",
                                      god->contract_terms_json),
      GNUNET_JSON_pack_string ("order_summary",
                               get_order_summary (god)),
      TALER_JSON_pack_amount ("refund_amount",
                              &god->refund_amount),
      TALER_JSON_pack_amount ("refund_taken",
                              &god->refund_taken));
    res = TALER_TEMPLATING_reply (
      god->sc.con,
      MHD_HTTP_OK,
      "show_order_details",
      god->hc->instance->settings.id,
      NULL,
      context);
    GNUNET_break (GNUNET_OK == res);
    json_decref (context);
    phase_end (god,
               (GNUNET_SYSERR == res)
               ? MHD_NO
               : MHD_YES);
  }
}


MHD_RESULT
TMH_get_orders_ID (const struct TMH_RequestHandler *rh,
                   struct MHD_Connection *connection,
                   struct TMH_HandlerContext *hc)
{
  struct GetOrderData *god = hc->ctx;

  (void) rh;
  if (NULL == god)
  {
    god = GNUNET_new (struct GetOrderData);
    hc->ctx = god;
    hc->cc = &god_cleanup;
    god->sc.con = connection;
    god->hc = hc;
    god->order_id = hc->infix;
    god->generate_html
      = TMH_MHD_test_html_desired (connection);

    /* first-time initialization / sanity checks */
    TALER_MHD_parse_request_arg_auto (connection,
                                      "h_contract",
                                      &god->h_contract_terms,
                                      god->h_contract_provided);
    TALER_MHD_parse_request_arg_auto (connection,
                                      "token",
                                      &god->claim_token,
                                      god->claim_token_provided);
    if (! (TALER_MHD_arg_to_yna (connection,
                                 "allow_refunded_for_repurchase",
                                 TALER_EXCHANGE_YNA_NO,
                                 &god->allow_refunded_for_repurchase)) )
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_BAD_REQUEST,
                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                         "allow_refunded_for_repurchase");
    god->session_id = MHD_lookup_connection_value (connection,
                                                   MHD_GET_ARGUMENT_KIND,
                                                   "session_id");

    /* process await_refund_obtained argument */
    {
      const char *await_refund_obtained_s;

      await_refund_obtained_s =
        MHD_lookup_connection_value (connection,
                                     MHD_GET_ARGUMENT_KIND,
                                     "await_refund_obtained");
      god->sc.awaiting_refund_obtained =
        (NULL != await_refund_obtained_s)
        ? 0 == strcasecmp (await_refund_obtained_s,
                           "yes")
        : false;
      if (god->sc.awaiting_refund_obtained)
        GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                    "Awaiting refund obtained\n");
    }

    TALER_MHD_parse_request_amount (connection,
                                    "refund",
                                    &god->sc.refund_expected);
    if (TALER_amount_is_valid (&god->sc.refund_expected))
    {
      god->sc.awaiting_refund = true;
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Awaiting minimum refund of %s\n",
                  TALER_amount2s (&god->sc.refund_expected));
    }
    TALER_MHD_parse_request_timeout (connection,
                                     &god->sc.long_poll_timeout);
  }

  if (GNUNET_SYSERR == god->suspended)
    return MHD_NO; /* we are in shutdown */
  if (GNUNET_YES == god->suspended)
  {
    god->suspended = GNUNET_NO;
    GNUNET_CONTAINER_DLL_remove (god_head,
                                 god_tail,
                                 god);
  }

  while (1)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Handling request in phase %d\n",
                (int) god->phase);
    switch (god->phase)
    {
    case GOP_INIT:
      phase_init (god);
      break;
    case GOP_LOOKUP_TERMS:
      phase_lookup_terms (god);
      break;
    case GOP_PARSE_CONTRACT:
      phase_parse_contract (god);
      break;
    case GOP_CHECK_CLIENT_ACCESS:
      phase_check_client_access (god);
      break;
    case GOP_CHECK_PAID:
      phase_check_paid (god);
      break;
    case GOP_REDIRECT_TO_PAID_ORDER:
      if (phase_redirect_to_paid_order (god))
        return MHD_YES;
      break;
    case GOP_HANDLE_UNPAID:
      if (phase_handle_unpaid (god))
        return MHD_YES;
      break;
    case GOP_CHECK_REFUNDED:
      if (phase_check_refunded (god))
        return MHD_YES;
      break;
    case GOP_RETURN_STATUS:
      phase_return_status (god);
      break;
    case GOP_RETURN_MHD_YES:
      return MHD_YES;
    case GOP_RETURN_MHD_NO:
      return MHD_NO;
    }
  }
}


/* end of taler-merchant-httpd_get-orders-ID.c */
