/*
  This file is part of TALER
  (C) 2014-2025 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_private-post-orders.c
 * @brief the POST /orders handler
 * @author Christian Grothoff
 * @author Marcello Stanisci
 * @author Christian Blättler
 */
#include "platform.h"
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_db_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <string.h>
#include <taler/taler_error_codes.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_util.h>
#include <taler_merchant_util.h>
#include <time.h>
#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_private-post-orders.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_contract.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_private-get-orders.h"

#include "taler_merchantdb_plugin.h"


/**
 * How often do we retry the simple INSERT database transaction?
 */
#define MAX_RETRIES 3

/**
 * Maximum number of inventory products per order.
 */
#define MAX_PRODUCTS 1024

/**
 * What is the label under which we find/place the merchant's
 * jurisdiction in the locations list by default?
 */
#define STANDARD_LABEL_MERCHANT_JURISDICTION "_mj"

/**
 * What is the label under which we find/place the merchant's
 * address in the locations list by default?
 */
#define STANDARD_LABEL_MERCHANT_ADDRESS "_ma"

/**
 * How long do we wait at most for /keys from the exchange(s)?
 * Ensures that we do not block forever just because some exchange
 * fails to respond *or* because our taler-merchant-keyscheck
 * refuses a forced download.
 */
#define MAX_KEYS_WAIT \
        GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500)

/**
 * Generate the base URL for the given merchant instance.
 *
 * @param connection the MHD connection
 * @param instance_id the merchant instance ID
 * @returns the merchant instance's base URL
 */
static char *
make_merchant_base_url (struct MHD_Connection *connection,
                        const char *instance_id)
{
  struct GNUNET_Buffer buf;

  if (GNUNET_OK !=
      TMH_base_url_by_connection (connection,
                                  instance_id,
                                  &buf))
    return NULL;
  GNUNET_buffer_write_path (&buf,
                            "");
  return GNUNET_buffer_reap_str (&buf);
}


/**
 * Information about a product we are supposed to add to the order
 * based on what we know it from our inventory.
 */
struct InventoryProduct
{
  /**
   * Identifier of the product in the inventory.
   */
  const char *product_id;

  /**
   * Number of units of the product to add to the order (integer part).
   */
  uint64_t quantity;

  /**
   * Fractional part of the quantity in units of 1/1000000 of the base value.
   */
  uint32_t quantity_frac;

  /**
   * True if the integer quantity field was missing in the request.
   */
  bool quantity_missing;

  /**
   * String representation of the quantity, if supplied.
   */
  const char *unit_quantity;

  /**
   * True if the string quantity field was missing in the request.
   */
  bool unit_quantity_missing;
};


/**
 * Handle for a rekey operation where we (re)request
 * the /keys from the exchange.
 */
struct RekeyExchange
{
  /**
   * Kept in a DLL.
   */
  struct RekeyExchange *prev;

  /**
   * Kept in a DLL.
   */
  struct RekeyExchange *next;

  /**
   * order this is for.
   */
  struct OrderContext *oc;

  /**
   * Base URL of the exchange.
   */
  char *url;

  /**
   * Request for keys.
   */
  struct TMH_EXCHANGES_KeysOperation *fo;

};


/**
 * Data structure where we evaluate the viability of a given
 * wire method for this order.
 */
struct WireMethodCandidate
{
  /**
   * Kept in a DLL.
   */
  struct WireMethodCandidate *next;

  /**
   * Kept in a DLL.
   */
  struct WireMethodCandidate *prev;

  /**
   * The wire method we are evaluating.
   */
  const struct TMH_WireMethod *wm;

  /**
   * List of exchanges to use when we use this wire method.
   */
  json_t *exchanges;

  /**
   * Array of maximum amounts that could be paid over all available exchanges
   * for this @a wm. Used to determine if this order creation requests exceeds
   * legal limits.
   */
  struct TALER_Amount *total_exchange_limits;

  /**
   * Length of the @e total_exchange_limits array.
   */
  unsigned int num_total_exchange_limits;

};


/**
 * Information we keep per order we are processing.
 */
struct OrderContext
{
  /**
   * Information set in the ORDER_PHASE_PARSE_REQUEST phase.
   */
  struct
  {
    /**
     * Order field of the request
     */
    json_t *order;

    /**
     * Set to how long refunds will be allowed.
     */
    struct GNUNET_TIME_Relative refund_delay;

    /**
     * RFC8905 payment target type to find a matching merchant account
     */
    const char *payment_target;

    /**
     * Shared key to use with @e pos_algorithm.
     */
    char *pos_key;

    /**
     * Selected algorithm (by template) when we are to
     * generate an OTP code for payment confirmation.
     */
    enum TALER_MerchantConfirmationAlgorithm pos_algorithm;

    /**
     * Hash of the POST request data, used to detect
     * idempotent requests.
     */
    struct TALER_MerchantPostDataHashP h_post_data;

    /**
     * Length of the @e inventory_products array.
     */
    unsigned int inventory_products_length;

    /**
     * Specifies that some products are to be included in the
     * order from the inventory. For these inventory management
     * is performed (so the products must be in stock).
     */
    struct InventoryProduct *inventory_products;

    /**
     * Length of the @e uuids array.
     */
    unsigned int uuids_length;

    /**
     * array of UUIDs used to reserve products from @a inventory_products.
     */
    struct GNUNET_Uuid *uuids;

    /**
     * Claim token for the request.
     */
    struct TALER_ClaimTokenP claim_token;

    /**
     * Session ID (optional) to use for the order.
     */
    const char *session_id;

  } parse_request;

  /**
   * Information set in the ORDER_PHASE_PARSE_ORDER phase.
   */
  struct
  {

    /**
     * Our order ID.
     */
    char *order_id;

    /**
     * Summary of the contract.
     */
    const char *summary;

    /**
     * Internationalized summary.
     */
    const json_t *summary_i18n;

    /**
     * URL that will show that the contract was successful
     * after it has been paid for.
     */
    const char *fulfillment_url;

    /**
    * Message shown to the customer after paying for the contract.
    * Either fulfillment_url or fulfillment_message must be specified.
    */
    const char *fulfillment_message;

    /**
     * Map from IETF BCP 47 language tags to localized fulfillment messages.
     */
    const json_t *fulfillment_message_i18n;

    /**
    * Array of products that are part of the purchase.
    */
    const json_t *products;

    /**
     * URL where the same contract could be ordered again (if available).
     */
    const char *public_reorder_url;

    /**
     * Merchant base URL.
     */
    char *merchant_base_url;

    /**
     * Timestamp of the order.
     */
    struct GNUNET_TIME_Timestamp timestamp;

    /**
     * Deadline for refunds.
     */
    struct GNUNET_TIME_Timestamp refund_deadline;

    /**
     * Payment deadline.
     */
    struct GNUNET_TIME_Timestamp pay_deadline;

    /**
     * Wire transfer deadline.
     */
    struct GNUNET_TIME_Timestamp wire_deadline;

    /**
     * Wire transfer round-up interval to apply.
     */
    enum GNUNET_TIME_RounderInterval wire_deadline_rounder;

    /**
     * Delivery date.
     */
    struct GNUNET_TIME_Timestamp delivery_date;

    /**
     * Delivery location.
     */
    const json_t *delivery_location;

    /**
     * Specifies for how long the wallet should try to get an
     * automatic refund for the purchase.
     */
    struct GNUNET_TIME_Relative auto_refund;

    /**
     * Nonce generated by the wallet and echoed by the merchant
     * in this field when the proposal is generated.
     */
    const char *nonce;

    /**
     * Extra data that is only interpreted by the merchant frontend.
     */
    const json_t *extra;

    /**
     * Minimum age required by the order.
     */
    uint32_t minimum_age;

    /**
     * Version of the contract terms.
     */
    enum TALER_MERCHANT_ContractVersion version;

    /**
     * Details present depending on @e version.
     */
    union
    {
      /**
       * Details only present for v0.
       */
      struct
      {
        /**
         * Gross amount value of the contract. Used to
         * compute @e max_stefan_fee.
         */
        struct TALER_Amount brutto;

        /**
         * Maximum fee as given by the client request.
         */
        struct TALER_Amount max_fee;
      } v0;

      /**
       * Details only present for v1.
       */
      struct
      {
        /**
         * Array of contract choices. Is null for v0 contracts.
         */
        const json_t *choices;
      } v1;
    } details;

  } parse_order;

  /**
   * Information set in the ORDER_PHASE_PARSE_CHOICES phase.
   */
  struct
  {
    /**
     * Array of possible specific contracts the wallet/customer may choose
     * from by selecting the respective index when signing the deposit
     * confirmation.
     */
    struct TALER_MERCHANT_ContractChoice *choices;

    /**
     * Length of the @e choices array.
     */
    unsigned int choices_len;

    /**
     * Array of token families referenced in the contract.
     */
    struct TALER_MERCHANT_ContractTokenFamily *token_families;

    /**
     * Length of the @e token_families array.
     */
    unsigned int token_families_len;
  } parse_choices;

  /**
   * Information set in the ORDER_PHASE_MERGE_INVENTORY phase.
   */
  struct
  {
    /**
     * Merged array of products in the @e order.
     */
    json_t *products;
  } merge_inventory;

  /**
   * Information set in the ORDER_PHASE_ADD_PAYMENT_DETAILS phase.
   */
  struct
  {

    /**
     * DLL of wire methods under evaluation.
     */
    struct WireMethodCandidate *wmc_head;

    /**
     * DLL of wire methods under evaluation.
     */
    struct WireMethodCandidate *wmc_tail;

    /**
     * Array of maximum amounts that appear in the contract choices
     * per currency.
     * Determines the maximum amounts that a client could pay for this
     * order and which we must thus make sure is acceptable for the
     * selected wire method/account if possible.
     */
    struct TALER_Amount *max_choice_limits;

    /**
     * Length of the @e max_choice_limits array.
     */
    unsigned int num_max_choice_limits;

    /**
     * Set to true if we may need an exchange. True if any amount is non-zero.
     */
    bool need_exchange;

  } add_payment_details;

  /**
   * Information set in the ORDER_PHASE_SELECT_WIRE_METHOD phase.
   */
  struct
  {

    /**
     * Array of exchanges we find acceptable for this order and wire method.
     */
    json_t *exchanges;

    /**
     * Wire method (and our bank account) we have selected
     * to be included for this order.
     */
    const struct TMH_WireMethod *wm;

  } select_wire_method;

  /**
   * Information set in the ORDER_PHASE_SET_EXCHANGES phase.
   */
  struct
  {

    /**
     * Forced requests to /keys to update our exchange
     * information.
     */
    struct RekeyExchange *pending_reload_head;

    /**
     * Forced requests to /keys to update our exchange
     * information.
     */
    struct RekeyExchange *pending_reload_tail;

    /**
     * How long do we wait at most until giving up on getting keys?
     */
    struct GNUNET_TIME_Absolute keys_timeout;

    /**
     * Task to wake us up on @e keys_timeout.
     */
    struct GNUNET_SCHEDULER_Task *wakeup_task;

    /**
     * Did we previously force reloading of /keys from
     * all exchanges? Set to 'true' to prevent us from
     * doing it again (and again...).
     */
    bool forced_reload;

    /**
     * Did we find a working exchange?
     */
    bool exchange_ok;

    /**
     * Did we find an exchange that justifies
     * reloading keys?
     */
    bool promising_exchange;

    /**
     * Set to true once we have attempted to load exchanges
     * for the first time.
     */
    bool exchanges_tried;

    /**
     * Details depending on the contract version.
     */
    union
    {

      /**
       * Details for contract v0.
       */
      struct
      {
        /**
         * Maximum fee for @e order based on STEFAN curves.
         * Used to set @e max_fee if not provided as part of
         * @e order.
         */
        struct TALER_Amount max_stefan_fee;

      } v0;

      /**
       * Details for contract v1.
       */
      struct
      {
        /**
         * Maximum fee for @e order based on STEFAN curves by
         * contract choice.
         * Used to set @e max_fee if not provided as part of
         * @e order.
         */
        struct TALER_Amount *max_stefan_fees;

      } v1;

    } details;

  } set_exchanges;

  /**
   * Information set in the ORDER_PHASE_SET_MAX_FEE phase.
   */
  struct
  {

    /**
     * Details depending on the contract version.
     */
    union
    {

      /**
       * Details for contract v0.
       */
      struct
      {
        /**
         * Maximum fee
         */
        struct TALER_Amount max_fee;
      } v0;

      /**
       * Details for contract v1.
       */
      struct
      {
        /**
         * Maximum fees by contract choice.
         */
        struct TALER_Amount *max_fees;

      } v1;

    } details;
  } set_max_fee;

  /**
   * Information set in the ORDER_PHASE_EXECUTE_ORDER phase.
   */
  struct
  {
    /**
     * Which product (by offset) is out of stock, UINT_MAX if all were in-stock.
     */
    unsigned int out_of_stock_index;

    /**
     * Set to a previous claim token *if* @e idempotent
     * is also true.
     */
    struct TALER_ClaimTokenP token;

    /**
     * Set to true if the order was idempotent and there
     * was an equivalent one before.
     */
    bool idempotent;

    /**
     * Set to true if the order is in conflict with a
     * previous order with the same order ID.
     */
    bool conflict;
  } execute_order;

  struct
  {
    /**
     * Contract terms to store in the database.
     */
    json_t *contract;
  } serialize_order;

  /**
   * Connection of the request.
   */
  struct MHD_Connection *connection;

  /**
   * Kept in a DLL while suspended.
   */
  struct OrderContext *next;

  /**
   * Kept in a DLL while suspended.
   */
  struct OrderContext *prev;

  /**
   * Handler context for the request.
   */
  struct TMH_HandlerContext *hc;

  /**
   * #GNUNET_YES if suspended.
   */
  enum GNUNET_GenericReturnValue suspended;

  /**
   * Current phase of setting up the order.
   */
  enum
  {
    ORDER_PHASE_PARSE_REQUEST,
    ORDER_PHASE_PARSE_ORDER,
    ORDER_PHASE_PARSE_CHOICES,
    ORDER_PHASE_MERGE_INVENTORY,
    ORDER_PHASE_ADD_PAYMENT_DETAILS,
    ORDER_PHASE_SET_EXCHANGES,
    ORDER_PHASE_SELECT_WIRE_METHOD,
    ORDER_PHASE_SET_MAX_FEE,
    ORDER_PHASE_SERIALIZE_ORDER,
    ORDER_PHASE_SALT_FORGETTABLE,
    ORDER_PHASE_CHECK_CONTRACT,
    ORDER_PHASE_EXECUTE_ORDER,

    /**
     * Processing is done, we should return #MHD_YES.
     */
    ORDER_PHASE_FINISHED_MHD_YES,

    /**
     * Processing is done, we should return #MHD_NO.
     */
    ORDER_PHASE_FINISHED_MHD_NO
  } phase;


};


/**
 * Kept in a DLL while suspended.
 */
static struct OrderContext *oc_head;

/**
 * Kept in a DLL while suspended.
 */
static struct OrderContext *oc_tail;


void
TMH_force_orders_resume ()
{
  struct OrderContext *oc;

  while (NULL != (oc = oc_head))
  {
    GNUNET_CONTAINER_DLL_remove (oc_head,
                                 oc_tail,
                                 oc);
    oc->suspended = GNUNET_SYSERR;
    MHD_resume_connection (oc->connection);
  }
}


/**
 * Add the given @a val to the @a array.  Adds the
 * amount to a given entry in @a array if one with the same
 * currency exists, otherwise extends the @a array.
 *
 * @param[in,out] array pointer to array of amounts
 * @param[in,out] array_len length of @a array
 * @param val amount to add
 * @param cap cap for the sums to enforce, can be NULL
 */
static void
add_to_currency_vector (struct TALER_Amount **array,
                        unsigned int *array_len,
                        const struct TALER_Amount *val,
                        const struct TALER_Amount *cap)
{
  for (unsigned int i = 0; i<*array_len; i++)
  {
    struct TALER_Amount *ai = &(*array)[i];

    if (GNUNET_OK ==
        TALER_amount_cmp_currency (ai,
                                   val))
    {
      enum TALER_AmountArithmeticResult aar;

      aar = TALER_amount_add (ai,
                              ai,
                              val);
      /* If we have a cap, we tolerate the overflow */
      GNUNET_assert ( (aar >= 0) ||
                      ( (TALER_AAR_INVALID_RESULT_OVERFLOW == aar) &&
                        (NULL != cap) ) );
      if (TALER_AAR_INVALID_RESULT_OVERFLOW == aar)
      {
        *ai = *cap;
      }
      else if (NULL != cap)
        GNUNET_assert (GNUNET_OK ==
                       TALER_amount_min (ai,
                                         ai,
                                         cap));
      return;
    }
  }
  GNUNET_array_append (*array,
                       *array_len,
                       *val);
  if (NULL != cap)
  {
    struct TALER_Amount *ai = &(*array)[(*array_len) - 1];

    GNUNET_assert (GNUNET_OK ==
                   TALER_amount_min (ai,
                                     ai,
                                     cap));
  }
}


/**
 * Update the phase of @a oc based on @a mret.
 *
 * @param[in,out] oc order to update phase for
 * @param mret #MHD_NO to close with #MHD_NO
 *             #MHD_YES to close with #MHD_YES
 */
static void
finalize_order (struct OrderContext *oc,
                MHD_RESULT mret)
{
  oc->phase = (MHD_YES == mret)
    ? ORDER_PHASE_FINISHED_MHD_YES
    : ORDER_PHASE_FINISHED_MHD_NO;
}


/**
 * Update the phase of @a oc based on @a ret.
 *
 * @param[in,out] oc order to update phase for
 * @param ret #GNUNET_SYSERR to close with #MHD_NO
 *            #GNUNET_NO to close with #MHD_YES
 *            #GNUNET_OK is not allowed!
 */
static void
finalize_order2 (struct OrderContext *oc,
                 enum GNUNET_GenericReturnValue ret)
{
  GNUNET_assert (GNUNET_OK != ret);
  oc->phase = (GNUNET_NO == ret)
    ? ORDER_PHASE_FINISHED_MHD_YES
    : ORDER_PHASE_FINISHED_MHD_NO;
}


/**
 * Generate an error response for @a oc.
 *
 * @param[in,out] oc order context to respond to
 * @param http_status HTTP status code to set
 * @param ec error code to set
 * @param detail error message detail to set
 */
static void
reply_with_error (struct OrderContext *oc,
                  unsigned int http_status,
                  enum TALER_ErrorCode ec,
                  const char *detail)
{
  MHD_RESULT mret;

  mret = TALER_MHD_reply_with_error (oc->connection,
                                     http_status,
                                     ec,
                                     detail);
  finalize_order (oc,
                  mret);
}


/**
 * Clean up memory used by @a wmc.
 *
 * @param[in,out] oc order context the WMC is part of
 * @param[in] wmc wire method candidate to free
 */
static void
free_wmc (struct OrderContext *oc,
          struct WireMethodCandidate *wmc)
{
  GNUNET_CONTAINER_DLL_remove (oc->add_payment_details.wmc_head,
                               oc->add_payment_details.wmc_tail,
                               wmc);
  GNUNET_array_grow (wmc->total_exchange_limits,
                     wmc->num_total_exchange_limits,
                     0);
  json_decref (wmc->exchanges);
  GNUNET_free (wmc);
}


/**
 * Clean up memory used by @a cls.
 *
 * @param[in] cls the `struct OrderContext` to clean up
 */
static void
clean_order (void *cls)
{
  struct OrderContext *oc = cls;
  struct RekeyExchange *rx;

  while (NULL != oc->add_payment_details.wmc_head)
    free_wmc (oc,
              oc->add_payment_details.wmc_head);
  while (NULL != (rx = oc->set_exchanges.pending_reload_head))
  {
    GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
                                 oc->set_exchanges.pending_reload_tail,
                                 rx);
    TMH_EXCHANGES_keys4exchange_cancel (rx->fo);
    GNUNET_free (rx->url);
    GNUNET_free (rx);
  }
  GNUNET_array_grow (oc->add_payment_details.max_choice_limits,
                     oc->add_payment_details.num_max_choice_limits,
                     0);
  if (NULL != oc->set_exchanges.wakeup_task)
  {
    GNUNET_SCHEDULER_cancel (oc->set_exchanges.wakeup_task);
    oc->set_exchanges.wakeup_task = NULL;
  }
  if (NULL != oc->select_wire_method.exchanges)
  {
    json_decref (oc->select_wire_method.exchanges);
    oc->select_wire_method.exchanges = NULL;
  }
  switch (oc->parse_order.version)
  {
  case TALER_MERCHANT_CONTRACT_VERSION_0:
    break;
  case TALER_MERCHANT_CONTRACT_VERSION_1:
    GNUNET_free (oc->set_max_fee.details.v1.max_fees);
    GNUNET_free (oc->set_exchanges.details.v1.max_stefan_fees);
    break;
  }
  if (NULL != oc->merge_inventory.products)
  {
    json_decref (oc->merge_inventory.products);
    oc->merge_inventory.products = NULL;
  }
  for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
  {
    TALER_MERCHANT_contract_choice_free (&oc->parse_choices.choices[i]);
  }
  GNUNET_array_grow (oc->parse_choices.choices,
                     oc->parse_choices.choices_len,
                     0);
  for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
  {
    struct TALER_MERCHANT_ContractTokenFamily *mctf
      = &oc->parse_choices.token_families[i];

    GNUNET_free (mctf->slug);
    GNUNET_free (mctf->name);
    GNUNET_free (mctf->description);
    json_decref (mctf->description_i18n);
    switch (mctf->kind)
    {
    case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
      GNUNET_break (0);
      break;
    case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
      for (size_t j = 0; j<mctf->details.subscription.trusted_domains_len; j++)
        GNUNET_free (mctf->details.subscription.trusted_domains[j]);
      GNUNET_free (mctf->details.subscription.trusted_domains);
      break;
    case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
      for (size_t j = 0; j<mctf->details.discount.expected_domains_len; j++)
        GNUNET_free (mctf->details.discount.expected_domains[j]);
      GNUNET_free (mctf->details.discount.expected_domains);
      break;
    }
    for (unsigned int j = 0; j<mctf->keys_len; j++)
    {
      GNUNET_CRYPTO_blind_sign_pub_decref (mctf->keys[j].pub.public_key);
    }
    GNUNET_array_grow (mctf->keys,
                       mctf->keys_len,
                       0);
  }
  GNUNET_array_grow (oc->parse_choices.token_families,
                     oc->parse_choices.token_families_len,
                     0);
  GNUNET_array_grow (oc->parse_request.inventory_products,
                     oc->parse_request.inventory_products_length,
                     0);
  GNUNET_array_grow (oc->parse_request.uuids,
                     oc->parse_request.uuids_length,
                     0);
  GNUNET_free (oc->parse_request.pos_key);
  json_decref (oc->parse_request.order);
  json_decref (oc->serialize_order.contract);
  GNUNET_free (oc->parse_order.order_id);
  GNUNET_free (oc->parse_order.merchant_base_url);
  GNUNET_free (oc);
}


/* ***************** ORDER_PHASE_EXECUTE_ORDER **************** */

/**
 * Compute remaining stock (integer and fractional parts) for a product.
 *
 * @param pd product details with current totals/sold/lost
 * @param[out] available_value remaining whole units (normalized, non-negative)
 * @param[out] available_frac remaining fractional units (0..MERCHANT_UNIT_FRAC_BASE-1)
 */
static void
compute_available_quantity (const struct TALER_MERCHANTDB_ProductDetails *pd,
                            uint64_t *available_value,
                            uint32_t *available_frac)
{
  int64_t value;
  int64_t frac;

  GNUNET_assert (NULL != available_value);
  GNUNET_assert (NULL != available_frac);

  if ( (INT64_MAX == pd->total_stock) &&
       (INT32_MAX == pd->total_stock_frac) )
  {
    *available_value = pd->total_stock;
    *available_frac = pd->total_stock_frac;
    return;
  }

  value = (int64_t) pd->total_stock
          - (int64_t) pd->total_sold
          - (int64_t) pd->total_lost;
  frac = (int64_t) pd->total_stock_frac
         - (int64_t) pd->total_sold_frac
         - (int64_t) pd->total_lost_frac;

  if (frac < 0)
  {
    int64_t borrow = ((-frac) + MERCHANT_UNIT_FRAC_BASE - 1)
                     / MERCHANT_UNIT_FRAC_BASE;
    value -= borrow;
    frac += borrow * (int64_t) MERCHANT_UNIT_FRAC_BASE;
  }
  else if (frac >= MERCHANT_UNIT_FRAC_BASE)
  {
    int64_t carry = frac / MERCHANT_UNIT_FRAC_BASE;
    value += carry;
    frac -= carry * (int64_t) MERCHANT_UNIT_FRAC_BASE;
  }

  if (value < 0)
  {
    value = 0;
    frac = 0;
  }

  *available_value = (uint64_t) value;
  *available_frac = (uint32_t) frac;
}


/**
 * Execute the database transaction to setup the order.
 *
 * @param[in,out] oc order context
 * @return transaction status, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a uuids were insufficient to reserve required inventory
 */
static enum GNUNET_DB_QueryStatus
execute_transaction (struct OrderContext *oc)
{
  enum GNUNET_DB_QueryStatus qs;
  struct GNUNET_TIME_Timestamp timestamp;
  uint64_t order_serial;

  if (GNUNET_OK !=
      TMH_db->start (TMH_db->cls,
                     "insert_order"))
  {
    GNUNET_break (0);
    return GNUNET_DB_STATUS_HARD_ERROR;
  }

  /* Test if we already have an order with this id */
  {
    json_t *contract_terms;
    struct TALER_MerchantPostDataHashP orig_post;

    qs = TMH_db->lookup_order (TMH_db->cls,
                               oc->hc->instance->settings.id,
                               oc->parse_order.order_id,
                               &oc->execute_order.token,
                               &orig_post,
                               &contract_terms);
    /* If yes, check for idempotency */
    if (0 > qs)
    {
      GNUNET_break (0);
      TMH_db->rollback (TMH_db->cls);
      return qs;
    }
    if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
    {
      TMH_db->rollback (TMH_db->cls);
      json_decref (contract_terms);
      /* Comparing the contract terms is sufficient because all the other
         params get added to it at some point. */
      if (0 == GNUNET_memcmp (&orig_post,
                              &oc->parse_request.h_post_data))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Order creation idempotent\n");
        oc->execute_order.idempotent = true;
        return qs;
      }
      GNUNET_break_op (0);
      oc->execute_order.conflict = true;
      return qs;
    }
  }

  /* Setup order */
  qs = TMH_db->insert_order (TMH_db->cls,
                             oc->hc->instance->settings.id,
                             oc->parse_order.order_id,
                             oc->parse_request.session_id,
                             &oc->parse_request.h_post_data,
                             oc->parse_order.pay_deadline,
                             &oc->parse_request.claim_token,
                             oc->serialize_order.contract,  /* called 'contract terms' at database. */
                             oc->parse_request.pos_key,
                             oc->parse_request.pos_algorithm);
  if (qs <= 0)
  {
    /* qs == 0: probably instance does not exist (anymore) */
    TMH_db->rollback (TMH_db->cls);
    return qs;
  }
  /* Migrate locks from UUIDs to new order: first release old locks */
  for (unsigned int i = 0; i<oc->parse_request.uuids_length; i++)
  {
    qs = TMH_db->unlock_inventory (TMH_db->cls,
                                   &oc->parse_request.uuids[i]);
    if (qs < 0)
    {
      TMH_db->rollback (TMH_db->cls);
      return qs;
    }
    /* qs == 0 is OK here, that just means we did not HAVE any lock under this
       UUID */
  }
  /* Migrate locks from UUIDs to new order: acquire new locks
     (note: this can basically ONLY fail on serializability OR
     because the UUID locks were insufficient for the desired
     quantities). */
  for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
  {
    qs = TMH_db->insert_order_lock (
      TMH_db->cls,
      oc->hc->instance->settings.id,
      oc->parse_order.order_id,
      oc->parse_request.inventory_products[i].product_id,
      oc->parse_request.inventory_products[i].quantity,
      oc->parse_request.inventory_products[i].quantity_frac);
    if (qs < 0)
    {
      TMH_db->rollback (TMH_db->cls);
      return qs;
    }
    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    {
      /* qs == 0: lock acquisition failed due to insufficient stocks */
      TMH_db->rollback (TMH_db->cls);
      oc->execute_order.out_of_stock_index = i; /* indicate which product is causing the issue */
      return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    }
  }
  oc->execute_order.out_of_stock_index = UINT_MAX;

  /* Get the order serial and timestamp for the order we just created to
     update long-poll clients. */
  qs = TMH_db->lookup_order_summary (TMH_db->cls,
                                     oc->hc->instance->settings.id,
                                     oc->parse_order.order_id,
                                     &timestamp,
                                     &order_serial);
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
  {
    TMH_db->rollback (TMH_db->cls);
    return qs;
  }

  {
    json_t *jhook;

    jhook = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_string ("order_id",
                               oc->parse_order.order_id),
      GNUNET_JSON_pack_object_incref ("contract",
                                      oc->serialize_order.contract),
      GNUNET_JSON_pack_string ("instance_id",
                               oc->hc->instance->settings.id)
      );
    GNUNET_assert (NULL != jhook);
    qs = TMH_trigger_webhook (oc->hc->instance->settings.id,
                              "order_created",
                              jhook);
    json_decref (jhook);
    if (0 > qs)
    {
      TMH_db->rollback (TMH_db->cls);
      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
        return qs;
      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_DB_STORE_FAILED,
                        "failed to trigger webhooks");
      return qs;
    }
  }

  TMH_notify_order_change (oc->hc->instance,
                           TMH_OSF_NONE,
                           timestamp,
                           order_serial);
  /* finally, commit transaction (note: if it fails, we ALSO re-acquire
     the UUID locks, which is exactly what we want) */
  qs = TMH_db->commit (TMH_db->cls);
  if (0 > qs)
    return qs;
  return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;   /* 1 == success! */
}


/**
 * Transform an order into a proposal and store it in the
 * database. Write the resulting proposal or an error message
 * of a MHD connection.
 *
 * @param[in,out] oc order context
 */
static void
phase_execute_order (struct OrderContext *oc)
{
  const struct TALER_MERCHANTDB_InstanceSettings *settings =
    &oc->hc->instance->settings;
  enum GNUNET_DB_QueryStatus qs;

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Executing database transaction to create order '%s' for instance '%s'\n",
              oc->parse_order.order_id,
              settings->id);
  for (unsigned int i = 0; i<MAX_RETRIES; i++)
  {
    TMH_db->preflight (TMH_db->cls);
    qs = execute_transaction (oc);
    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
      break;
  }
  if (0 >= qs)
  {
    /* Special report if retries insufficient */
    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    {
      GNUNET_break (0);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
                        NULL);
      return;
    }
    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    {
      /* should be: contract (!) with same order ID
         already exists */
      reply_with_error (
        oc,
        MHD_HTTP_CONFLICT,
        TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
        oc->parse_order.order_id);
      return;
    }
    /* Other hard transaction error (disk full, etc.) */
    GNUNET_break (0);
    reply_with_error (
      oc,
      MHD_HTTP_INTERNAL_SERVER_ERROR,
      TALER_EC_GENERIC_DB_COMMIT_FAILED,
      NULL);
    return;
  }

  /* DB transaction succeeded, check for idempotent */
  if (oc->execute_order.idempotent)
  {
    MHD_RESULT ret;

    ret = TALER_MHD_REPLY_JSON_PACK (
      oc->connection,
      MHD_HTTP_OK,
      GNUNET_JSON_pack_string ("order_id",
                               oc->parse_order.order_id),
      GNUNET_JSON_pack_timestamp ("pay_deadline",
                                  oc->parse_order.pay_deadline),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_data_varsize (
          "token",
          GNUNET_is_zero (&oc->execute_order.token)
          ? NULL
          : &oc->execute_order.token,
          sizeof (oc->execute_order.token))));
    finalize_order (oc,
                    ret);
    return;
  }
  if (oc->execute_order.conflict)
  {
    reply_with_error (
      oc,
      MHD_HTTP_CONFLICT,
      TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
      oc->parse_order.order_id);
    return;
  }

  /* DB transaction succeeded, check for out-of-stock */
  if (oc->execute_order.out_of_stock_index < UINT_MAX)
  {
    /* We had a product that has insufficient quantities,
       generate the details for the response. */
    struct TALER_MERCHANTDB_ProductDetails pd;
    MHD_RESULT ret;
    const struct InventoryProduct *ip;
    size_t num_categories = 0;
    uint64_t *categories = NULL;
    uint64_t available_quantity;
    uint32_t available_quantity_frac;
    char requested_quantity_buf[64];
    char available_quantity_buf[64];

    ip = &oc->parse_request.inventory_products[
      oc->execute_order.out_of_stock_index];
    memset (&pd,
            0,
            sizeof (pd));
    qs = TMH_db->lookup_product (
      TMH_db->cls,
      oc->hc->instance->settings.id,
      ip->product_id,
      &pd,
      &num_categories,
      &categories);
    switch (qs)
    {
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      GNUNET_free (categories);
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Order creation failed: product out of stock\n");

      compute_available_quantity (&pd,
                                  &available_quantity,
                                  &available_quantity_frac);
      TMH_format_fractional_string (TMH_VK_QUANTITY,
                                    ip->quantity,
                                    ip->quantity_frac,
                                    sizeof (requested_quantity_buf),
                                    requested_quantity_buf);
      TMH_format_fractional_string (TMH_VK_QUANTITY,
                                    available_quantity,
                                    available_quantity_frac,
                                    sizeof (available_quantity_buf),
                                    available_quantity_buf);
      ret = TALER_MHD_REPLY_JSON_PACK (
        oc->connection,
        MHD_HTTP_GONE,
        GNUNET_JSON_pack_string (
          "product_id",
          ip->product_id),
        GNUNET_JSON_pack_uint64 (
          "requested_quantity",
          ip->quantity),
        GNUNET_JSON_pack_string (
          "unit_requested_quantity",
          requested_quantity_buf),
        GNUNET_JSON_pack_uint64 (
          "available_quantity",
          available_quantity),
        GNUNET_JSON_pack_string (
          "unit_available_quantity",
          available_quantity_buf),
        GNUNET_JSON_pack_allow_null (
          GNUNET_JSON_pack_timestamp (
            "restock_expected",
            pd.next_restock)));
      TALER_MERCHANTDB_product_details_free (&pd);
      finalize_order (oc,
                      ret);
      return;
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Order creation failed: unknown product out of stock\n");
      finalize_order (oc,
                      TALER_MHD_REPLY_JSON_PACK (
                        oc->connection,
                        MHD_HTTP_GONE,
                        GNUNET_JSON_pack_string (
                          "product_id",
                          ip->product_id),
                        GNUNET_JSON_pack_uint64 (
                          "requested_quantity",
                          ip->quantity),
                        GNUNET_JSON_pack_uint64 (
                          "available_quantity",
                          0)));
      return;
    case GNUNET_DB_STATUS_SOFT_ERROR:
      GNUNET_break (0);
      reply_with_error (
        oc,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        TALER_EC_GENERIC_DB_SOFT_FAILURE,
        NULL);
      return;
    case GNUNET_DB_STATUS_HARD_ERROR:
      GNUNET_break (0);
      reply_with_error (
        oc,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        TALER_EC_GENERIC_DB_FETCH_FAILED,
        NULL);
      return;
    }
    GNUNET_break (0);
    oc->phase = ORDER_PHASE_FINISHED_MHD_NO;
    return;
  } /* end 'out of stock' case */

  /* Everything in-stock, generate positive response */
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Order creation succeeded\n");

  {
    MHD_RESULT ret;

    ret = TALER_MHD_REPLY_JSON_PACK (
      oc->connection,
      MHD_HTTP_OK,
      GNUNET_JSON_pack_string ("order_id",
                               oc->parse_order.order_id),
      GNUNET_JSON_pack_allow_null (
        GNUNET_JSON_pack_data_varsize (
          "token",
          GNUNET_is_zero (&oc->parse_request.claim_token)
          ? NULL
          : &oc->parse_request.claim_token,
          sizeof (oc->parse_request.claim_token))));
    finalize_order (oc,
                    ret);
  }
}


/* ***************** ORDER_PHASE_CHECK_CONTRACT **************** */


/**
 * Check that the contract is now well-formed. Upon success, continue
 * processing with execute_order().
 *
 * @param[in,out] oc order context
 */
static void
phase_check_contract (struct OrderContext *oc)
{
  struct TALER_PrivateContractHashP h_control;

  switch (TALER_JSON_contract_hash (oc->serialize_order.contract,
                                    &h_control))
  {
  case GNUNET_SYSERR:
    GNUNET_break (0);
    reply_with_error (
      oc,
      MHD_HTTP_INTERNAL_SERVER_ERROR,
      TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
      "could not compute hash of serialized order");
    return;
  case GNUNET_NO:
    GNUNET_break_op (0);
    reply_with_error (
      oc,
      MHD_HTTP_BAD_REQUEST,
      TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
      "order contained unallowed values");
    return;
  case GNUNET_OK:
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Contract hash is %s\n",
                GNUNET_h2s (&h_control.hash));
    oc->phase++;
    return;
  }
  GNUNET_assert (0);
}


/* ***************** ORDER_PHASE_SALT_FORGETTABLE **************** */


/**
 * Modify the final contract terms adding salts for
 * items that are forgettable.
 *
 * @param[in,out] oc order context
 */
static void
phase_salt_forgettable (struct OrderContext *oc)
{
  if (GNUNET_OK !=
      TALER_JSON_contract_seed_forgettable (oc->parse_request.order,
                                            oc->serialize_order.contract))
  {
    GNUNET_break_op (0);
    reply_with_error (
      oc,
      MHD_HTTP_BAD_REQUEST,
      TALER_EC_GENERIC_JSON_INVALID,
      "could not compute hash of order due to bogus forgettable fields");
    return;
  }
  oc->phase++;
}


/* ***************** ORDER_PHASE_SERIALIZE_ORDER **************** */

/**
 * Get rounded time interval. @a start is calculated by rounding
 * @a ts down to the nearest multiple of @a precision.
 *
 * @param precision rounding precision.
 *        year, month, day, hour, minute are supported.
 * @param ts timestamp to round
 * @param[out] start start of the interval
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 */
static enum GNUNET_GenericReturnValue
get_rounded_time_interval_down (struct GNUNET_TIME_Relative precision,
                                struct GNUNET_TIME_Timestamp ts,
                                struct GNUNET_TIME_Timestamp *start)
{
  enum GNUNET_TIME_RounderInterval ri;

  ri = GNUNET_TIME_relative_to_round_interval (precision);
  if ( (GNUNET_TIME_RI_NONE == ri) &&
       (! GNUNET_TIME_relative_is_zero (precision)) )
  {
    *start = ts;
    return GNUNET_SYSERR;
  }
  *start = GNUNET_TIME_absolute_to_timestamp (
    GNUNET_TIME_round_down (ts.abs_time,
                            ri));
  return GNUNET_OK;
}


/**
 * Get rounded time interval. @a start is calculated by rounding
 * @a ts up to the nearest multiple of @a precision.
 *
 * @param precision rounding precision.
 *        year, month, day, hour, minute are supported.
 * @param ts timestamp to round
 * @param[out] start start of the interval
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 */
static enum GNUNET_GenericReturnValue
get_rounded_time_interval_up (struct GNUNET_TIME_Relative precision,
                              struct GNUNET_TIME_Timestamp ts,
                              struct GNUNET_TIME_Timestamp *start)
{
  enum GNUNET_TIME_RounderInterval ri;

  ri = GNUNET_TIME_relative_to_round_interval (precision);
  if ( (GNUNET_TIME_RI_NONE == ri) &&
       (! GNUNET_TIME_relative_is_zero (precision)) )
  {
    *start = ts;
    return GNUNET_SYSERR;
  }
  *start = GNUNET_TIME_absolute_to_timestamp (
    GNUNET_TIME_round_up (ts.abs_time,
                          ri));
  return GNUNET_OK;
}


/**
 * Find the family entry for the family of the given @a slug
 * in @a oc.
 *
 * @param[in] oc order context to search
 * @param slug slug to search for
 * @return NULL if @a slug was not found
 */
static struct TALER_MERCHANT_ContractTokenFamily *
find_family (const struct OrderContext *oc,
             const char *slug)
{
  for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
  {
    if (0 == strcmp (oc->parse_choices.token_families[i].slug,
                     slug))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Token family %s already in order\n",
                  slug);
      return &oc->parse_choices.token_families[i];
    }
  }
  return NULL;
}


/**
 * Function called with each applicable family key that should
 * be added to the respective token family of the order.
 *
 * @param cls a `struct OrderContext *` to expand
 * @param tfkd token family key details to add to the contract
 */
static void
add_family_key (void *cls,
                const struct TALER_MERCHANTDB_TokenFamilyKeyDetails *tfkd)
{
  struct OrderContext *oc = cls;
  const struct TALER_MERCHANTDB_TokenFamilyDetails *tf = &tfkd->token_family;
  struct TALER_MERCHANT_ContractTokenFamily *family;

  family = find_family (oc,
                        tf->slug);
  if (NULL == family)
  {
    /* Family not yet in our contract terms, create new entry */
    struct TALER_MERCHANT_ContractTokenFamily new_family = {
      .slug = GNUNET_strdup (tf->slug),
      .name = GNUNET_strdup (tf->name),
      .description = GNUNET_strdup (tf->description),
      .description_i18n = json_incref (tf->description_i18n),
    };

    switch (tf->kind)
    {
    case TALER_MERCHANTDB_TFK_Subscription:
      {
        json_t *tdomains = json_object_get (tf->extra_data,
                                            "trusted_domains");
        json_t *dom;
        size_t i;

        new_family.kind = TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION;
        new_family.critical = true;
        new_family.details.subscription.trusted_domains_len
          = json_array_size (tdomains);
        GNUNET_assert (new_family.details.subscription.trusted_domains_len
                       < UINT_MAX);
        new_family.details.subscription.trusted_domains
          = GNUNET_new_array (
              new_family.details.subscription.trusted_domains_len,
              char *);
        json_array_foreach (tdomains, i, dom)
        {
          const char *val;

          val = json_string_value (dom);
          GNUNET_break (NULL != val);
          if (NULL != val)
            new_family.details.subscription.trusted_domains[i]
              = GNUNET_strdup (val);
        }
        break;
      }
    case TALER_MERCHANTDB_TFK_Discount:
      {
        json_t *edomains = json_object_get (tf->extra_data,
                                            "expected_domains");
        json_t *dom;
        size_t i;

        new_family.kind = TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT;
        new_family.critical = false;
        new_family.details.discount.expected_domains_len
          = json_array_size (edomains);
        GNUNET_assert (new_family.details.discount.expected_domains_len
                       < UINT_MAX);
        new_family.details.discount.expected_domains
          = GNUNET_new_array (
              new_family.details.discount.expected_domains_len,
              char *);
        json_array_foreach (edomains, i, dom)
        {
          const char *val;

          val = json_string_value (dom);
          GNUNET_break (NULL != val);
          if (NULL != val)
            new_family.details.discount.expected_domains[i]
              = GNUNET_strdup (val);
        }
        break;
      }
    }
    GNUNET_array_append (oc->parse_choices.token_families,
                         oc->parse_choices.token_families_len,
                         new_family);
    family = &oc->parse_choices.token_families[
      oc->parse_choices.token_families_len - 1];
  }
  if (NULL == tfkd->pub.public_key)
    return;
  for (unsigned int i = 0; i<family->keys_len; i++)
  {
    if (TALER_token_issue_pub_cmp (&family->keys[i].pub,
                                   &tfkd->pub))
    {
      /* A matching key is already in the list. */
      return;
    }
  }

  {
    struct TALER_MERCHANT_ContractTokenFamilyKey key;

    TALER_token_issue_pub_copy (&key.pub,
                                &tfkd->pub);
    key.valid_after = tfkd->signature_validity_start;
    key.valid_before = tfkd->signature_validity_end;
    GNUNET_array_append (family->keys,
                         family->keys_len,
                         key);
  }
}


/**
 * Check if the token family with the given @a slug is already present in the
 * list of token families for this order. If not, fetch its details and add it
 * to the list.
 *
 * @param[in,out] oc order context
 * @param slug slug of the token family
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 */
static enum GNUNET_GenericReturnValue
add_input_token_family (struct OrderContext *oc,
                        const char *slug)
{
  struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
  struct GNUNET_TIME_Timestamp end = oc->parse_order.pay_deadline;
  enum GNUNET_DB_QueryStatus qs;
  enum TALER_ErrorCode ec = TALER_EC_INVALID; /* make compiler happy */
  unsigned int http_status = 0; /* make compiler happy */

  qs = TMH_db->lookup_token_family_keys (TMH_db->cls,
                                         oc->hc->instance->settings.id,
                                         slug,
                                         now,
                                         end,
                                         &add_family_key,
                                         oc);
  switch (qs)
  {
  case GNUNET_DB_STATUS_HARD_ERROR:
    GNUNET_break (0);
    http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
    break;
  case GNUNET_DB_STATUS_SOFT_ERROR:
    GNUNET_break (0);
    http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
    break;
  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Input token family slug %s unknown\n",
                slug);
    http_status = MHD_HTTP_NOT_FOUND;
    ec = TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN;
    break;
  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    return GNUNET_OK;
  }
  reply_with_error (oc,
                    http_status,
                    ec,
                    slug);
  return GNUNET_SYSERR;
}


/**
 * Find the index of a key in the @a family that is valid at
 * the time @a valid_at.
 *
 * @param family to search
 * @param valid_at time when the key must be valid
 * @param[out] key_index index to initialize
 * @return #GNUNET_OK if a matching key was found
 */
static enum GNUNET_GenericReturnValue
find_key_index (struct TALER_MERCHANT_ContractTokenFamily *family,
                struct GNUNET_TIME_Timestamp valid_at,
                unsigned int *key_index)
{
  for (unsigned int i = 0; i<family->keys_len; i++)
  {
    if ( (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_after,
                                     <=,
                                     valid_at)) &&
         (GNUNET_TIME_timestamp_cmp (family->keys[i].valid_before,
                                     >=,
                                     valid_at)) )
    {
      /* The token family and a matching key already exist. */
      *key_index = i;
      return GNUNET_OK;
    }
  }
  return GNUNET_NO;
}


/**
 * Create fresh key pair based on @a cipher_spec.
 *
 * @param cipher_spec which kind of key pair should we generate
 * @param[out] priv set to new private key
 * @param[out] pub set to new public key
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
create_key (const char *cipher_spec,
            struct TALER_TokenIssuePrivateKey *priv,
            struct TALER_TokenIssuePublicKey *pub)
{
  unsigned int len;
  char dummy;

  if (0 == strcmp ("cs",
                   cipher_spec))
  {
    GNUNET_CRYPTO_blind_sign_keys_create (
      &priv->private_key,
      &pub->public_key,
      GNUNET_CRYPTO_BSA_CS);
    return GNUNET_OK;
  }
  if (1 ==
      sscanf (cipher_spec,
              "rsa(%u)%c",
              &len,
              &dummy))
  {
    GNUNET_CRYPTO_blind_sign_keys_create (
      &priv->private_key,
      &pub->public_key,
      GNUNET_CRYPTO_BSA_RSA,
      len);
    return GNUNET_OK;
  }
  return GNUNET_SYSERR;
}


/**
 * Check if the token family with the given @a slug is already present in the
 * list of token families for this order. If not, fetch its details and add it
 * to the list. Also checks if there is a public key with that expires after
 * the payment deadline.  If not, generates a new key pair and stores it in
 * the database.
 *
 * @param[in,out] oc order context
 * @param slug slug of the token family
 * @param valid_at time when the token returned must be valid
 * @param[out] key_index set to the index of the respective public
 *    key in the @a slug's token family keys array.
 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
 */
static enum GNUNET_GenericReturnValue
add_output_token_family (struct OrderContext *oc,
                         const char *slug,
                         struct GNUNET_TIME_Timestamp valid_at,
                         unsigned int *key_index)
{
  struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details;
  struct TALER_MERCHANT_ContractTokenFamily *family;
  enum GNUNET_DB_QueryStatus qs;

  family = find_family (oc,
                        slug);
  if ( (NULL != family) &&
       (GNUNET_OK ==
        find_key_index (family,
                        valid_at,
                        key_index)) )
    return GNUNET_OK;
  qs = TMH_db->lookup_token_family_key (TMH_db->cls,
                                        oc->hc->instance->settings.id,
                                        slug,
                                        valid_at,
                                        oc->parse_order.pay_deadline,
                                        &key_details);
  switch (qs)
  {
  case GNUNET_DB_STATUS_HARD_ERROR:
    GNUNET_break (0);
    reply_with_error (oc,
                      MHD_HTTP_INTERNAL_SERVER_ERROR,
                      TALER_EC_GENERIC_DB_FETCH_FAILED,
                      "lookup_token_family_key");
    return GNUNET_SYSERR;
  case GNUNET_DB_STATUS_SOFT_ERROR:
    /* Single-statement transaction shouldn't possibly cause serialization errors.
       Thus treating like a hard error. */
    GNUNET_break (0);
    reply_with_error (oc,
                      MHD_HTTP_INTERNAL_SERVER_ERROR,
                      TALER_EC_GENERIC_DB_SOFT_FAILURE,
                      "lookup_token_family_key");
    return GNUNET_SYSERR;
  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Output token family slug %s unknown\n",
                slug);
    reply_with_error (oc,
                      MHD_HTTP_NOT_FOUND,
                      TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN,
                      slug);
    return GNUNET_SYSERR;
  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    break;
  }

  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Lookup of token family %s at %llu yielded %s\n",
              slug,
              (unsigned long long) valid_at.abs_time.abs_value_us,
              NULL == key_details.pub.public_key ? "no key" : "a key");

  if (NULL == family)
  {
    add_family_key (oc,
                    &key_details);
    family = find_family (oc,
                          slug);
    GNUNET_assert (NULL != family);
  }
  /* we don't need the full family details anymore */
  GNUNET_free (key_details.token_family.slug);
  GNUNET_free (key_details.token_family.name);
  GNUNET_free (key_details.token_family.description);
  json_decref (key_details.token_family.description_i18n);
  json_decref (key_details.token_family.extra_data);

  if (NULL != key_details.pub.public_key)
  {
    /* lookup_token_family_key must have found a matching key,
       and it must have been added. Find and use the index. */
    GNUNET_CRYPTO_blind_sign_pub_decref (key_details.pub.public_key);
    GNUNET_CRYPTO_blind_sign_priv_decref (key_details.priv.private_key);
    GNUNET_free (key_details.token_family.cipher_spec);
    GNUNET_assert (GNUNET_OK ==
                   find_key_index (family,
                                   valid_at,
                                   key_index));
    return GNUNET_OK;
  }

  /* No suitable key exists, create one! */
  {
    struct TALER_MERCHANT_ContractTokenFamilyKey key;
    enum GNUNET_DB_QueryStatus iqs;
    struct TALER_TokenIssuePrivateKey token_priv;
    struct GNUNET_TIME_Timestamp key_expires;
    struct GNUNET_TIME_Timestamp round_start;

    if (GNUNET_OK !=
        get_rounded_time_interval_down (
          key_details.token_family.validity_granularity,
          valid_at,
          &round_start))
    {
      GNUNET_break (0);
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Unsupported validity granularity interval %s found in database for token family %s!\n",
                  GNUNET_TIME_relative2s (
                    key_details.token_family.validity_granularity,
                    false),
                  slug);
      GNUNET_free (key_details.token_family.cipher_spec);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                        "get_rounded_time_interval_down failed");
      return GNUNET_SYSERR;
    }
    if (GNUNET_TIME_relative_cmp (
          key_details.token_family.duration,
          <,
          GNUNET_TIME_relative_add (
            key_details.token_family.validity_granularity,
            key_details.token_family.start_offset)))
    {
      GNUNET_break (0);
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Inconsistent duration %s found in database for token family %s (below validity granularity plus start_offset)!\n",
                  GNUNET_TIME_relative2s (key_details.token_family.duration,
                                          false),
                  slug);
      GNUNET_free (key_details.token_family.cipher_spec);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                        "duration, validty_granularity and start_offset inconsistent for token family");
      return GNUNET_SYSERR;
    }
    key.valid_after
      = GNUNET_TIME_timestamp_max (
          GNUNET_TIME_absolute_to_timestamp (
            GNUNET_TIME_absolute_subtract (
              round_start.abs_time,
              key_details.token_family.start_offset)),
          key_details.token_family.valid_after);
    key.valid_before
      = GNUNET_TIME_timestamp_min (
          GNUNET_TIME_absolute_to_timestamp (
            GNUNET_TIME_absolute_add (
              key.valid_after.abs_time,
              key_details.token_family.duration)),
          key_details.token_family.valid_before);
    GNUNET_assert (GNUNET_OK ==
                   get_rounded_time_interval_down (
                     key_details.token_family.validity_granularity,
                     key.valid_before,
                     &key_expires));
    if (GNUNET_TIME_timestamp_cmp (
          key_expires,
          ==,
          round_start))
    {
      /* valid_before does not actually end after the
         next rounded validity period would start;
         determine next rounded validity period
         start point and extend valid_before to cover
         the full validity period */
      GNUNET_assert (
        GNUNET_OK ==
        get_rounded_time_interval_up (
          key_details.token_family.validity_granularity,
          key.valid_before,
          &key_expires));
      /* This should basically always end up being key_expires */
      key.valid_before = GNUNET_TIME_timestamp_max (key.valid_before,
                                                    key_expires);
    }
    if (GNUNET_OK !=
        create_key (key_details.token_family.cipher_spec,
                    &token_priv,
                    &key.pub))
    {
      GNUNET_break (0);
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Unsupported cipher family %s found in database for token family %s!\n",
                  key_details.token_family.cipher_spec,
                  slug);
      GNUNET_free (key_details.token_family.cipher_spec);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                        "invalid cipher stored in local database for token family");
      return GNUNET_SYSERR;
    }
    GNUNET_free (key_details.token_family.cipher_spec);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Storing new key for slug %s of %s\n",
                slug,
                oc->hc->instance->settings.id);
    iqs = TMH_db->insert_token_family_key (TMH_db->cls,
                                           oc->hc->instance->settings.id,
                                           slug,
                                           &key.pub,
                                           &token_priv,
                                           key_expires,
                                           key.valid_after,
                                           key.valid_before);
    GNUNET_CRYPTO_blind_sign_priv_decref (token_priv.private_key);
    switch (iqs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
      GNUNET_break (0);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_DB_STORE_FAILED,
                        NULL);
      return GNUNET_SYSERR;
    case GNUNET_DB_STATUS_SOFT_ERROR:
      /* Single-statement transaction shouldn't possibly cause serialization errors.
         Thus treating like a hard error. */
      GNUNET_break (0);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
                        NULL);
      return GNUNET_SYSERR;
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      GNUNET_break (0);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_DB_STORE_FAILED,
                        NULL);
      return GNUNET_SYSERR;
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      break;
    }
    *key_index = family->keys_len;
    GNUNET_array_append (family->keys,
                         family->keys_len,
                         key);
  }
  return GNUNET_OK;
}


/**
 * Build JSON array that represents all of the token families
 * in the contract.
 *
 * @param[in] oc v1-style order context
 * @return JSON array with token families for the contract
 */
static json_t *
output_token_families (struct OrderContext *oc)
{
  json_t *token_families = json_object ();

  GNUNET_assert (NULL != token_families);
  for (unsigned int i = 0; i<oc->parse_choices.token_families_len; i++)
  {
    const struct TALER_MERCHANT_ContractTokenFamily *family
      = &oc->parse_choices.token_families[i];
    json_t *jfamily;

    jfamily = TALER_MERCHANT_json_from_token_family (family);

    GNUNET_assert (jfamily != NULL);

    GNUNET_assert (0 ==
                   json_object_set_new (token_families,
                                        family->slug,
                                        jfamily));
  }
  return token_families;
}


/**
 * Build JSON array that represents all of the contract choices
 * in the contract.
 *
 * @param[in] oc v1-style order context
 * @return JSON array with token families for the contract
 */
static json_t *
output_contract_choices (struct OrderContext *oc)
{
  json_t *choices = json_array ();

  GNUNET_assert (NULL != choices);
  for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
  {
    oc->parse_choices.choices[i].max_fee =
      oc->set_max_fee.details.v1.max_fees[i];
    GNUNET_assert (0 == json_array_append_new (
                     choices,
                     TALER_MERCHANT_json_from_contract_choice (
                       &oc->parse_choices.choices[i],
                       false)));
  }

  return choices;
}


/**
 * Serialize order into @a oc->serialize_order.contract,
 * ready to be stored in the database. Upon success, continue
 * processing with check_contract().
 *
 * @param[in,out] oc order context
 */
static void
phase_serialize_order (struct OrderContext *oc)
{
  const struct TALER_MERCHANTDB_InstanceSettings *settings =
    &oc->hc->instance->settings;
  json_t *merchant;

  merchant = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_string ("name",
                             settings->name),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("website",
                               settings->website)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("email",
                               settings->email)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("logo",
                               settings->logo)));
  GNUNET_assert (NULL != merchant);
  {
    json_t *loca;

    /* Handle merchant address */
    loca = settings->address;
    if (NULL != loca)
    {
      loca = json_deep_copy (loca);
      GNUNET_assert (NULL != loca);
      GNUNET_assert (0 ==
                     json_object_set_new (merchant,
                                          "address",
                                          loca));
    }
  }
  {
    json_t *juri;

    /* Handle merchant jurisdiction */
    juri = settings->jurisdiction;
    if (NULL != juri)
    {
      juri = json_deep_copy (juri);
      GNUNET_assert (NULL != juri);
      GNUNET_assert (0 ==
                     json_object_set_new (merchant,
                                          "jurisdiction",
                                          juri));
    }
  }

  oc->serialize_order.contract = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_int64 ("version",
                            oc->parse_order.version),
    GNUNET_JSON_pack_string ("summary",
                             oc->parse_order.summary),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_object_incref (
        "summary_i18n",
        (json_t *) oc->parse_order.summary_i18n)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("public_reorder_url",
                               oc->parse_order.public_reorder_url)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("fulfillment_message",
                               oc->parse_order.fulfillment_message)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_object_incref (
        "fulfillment_message_i18n",
        (json_t *) oc->parse_order.fulfillment_message_i18n)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("fulfillment_url",
                               oc->parse_order.fulfillment_url)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_uint64 ("minimum_age",
                               oc->parse_order.minimum_age)),
    GNUNET_JSON_pack_array_incref ("products",
                                   oc->merge_inventory.products),
    GNUNET_JSON_pack_data_auto ("h_wire",
                                &oc->select_wire_method.wm->h_wire),
    GNUNET_JSON_pack_string ("wire_method",
                             oc->select_wire_method.wm->wire_method),
    GNUNET_JSON_pack_string ("order_id",
                             oc->parse_order.order_id),
    GNUNET_JSON_pack_timestamp ("timestamp",
                                oc->parse_order.timestamp),
    GNUNET_JSON_pack_timestamp ("pay_deadline",
                                oc->parse_order.pay_deadline),
    GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
                                oc->parse_order.wire_deadline),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_timestamp ("delivery_date",
                                  oc->parse_order.delivery_date)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_object_incref (
        "delivery_location",
        (json_t *) oc->parse_order.delivery_location)),
    GNUNET_JSON_pack_string ("merchant_base_url",
                             oc->parse_order.merchant_base_url),
    GNUNET_JSON_pack_object_steal ("merchant",
                                   merchant),
    GNUNET_JSON_pack_data_auto ("merchant_pub",
                                &oc->hc->instance->merchant_pub),
    GNUNET_JSON_pack_array_incref ("exchanges",
                                   oc->select_wire_method.exchanges),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_object_incref ("extra",
                                      (json_t *) oc->parse_order.extra))
    );

  {
    json_t *xtra;

    switch (oc->parse_order.version)
    {
    case TALER_MERCHANT_CONTRACT_VERSION_0:
      xtra = GNUNET_JSON_PACK (
        TALER_JSON_pack_amount ("max_fee",
                                &oc->set_max_fee.details.v0.max_fee),
        TALER_JSON_pack_amount ("amount",
                                &oc->parse_order.details.v0.brutto));
      break;
    case TALER_MERCHANT_CONTRACT_VERSION_1:
      {
        json_t *token_families = output_token_families (oc);
        json_t *choices = output_contract_choices (oc);

        if ( (NULL == token_families) ||
             (NULL == choices) )
        {
          GNUNET_break (0);
          return;
        }
        xtra = GNUNET_JSON_PACK (
          GNUNET_JSON_pack_array_steal ("choices",
                                        choices),
          GNUNET_JSON_pack_object_steal ("token_families",
                                         token_families));
        break;
      }
    default:
      GNUNET_assert (0);
    }
    GNUNET_assert (0 ==
                   json_object_update (oc->serialize_order.contract,
                                       xtra));
    json_decref (xtra);
  }


  /* Pack does not work here, because it doesn't set zero-values for timestamps */
  GNUNET_assert (0 ==
                 json_object_set_new (oc->serialize_order.contract,
                                      "refund_deadline",
                                      GNUNET_JSON_from_timestamp (
                                        oc->parse_order.refund_deadline)));
  /* auto_refund should only be set if it is not 0 */
  if (! GNUNET_TIME_relative_is_zero (oc->parse_order.auto_refund))
  {
    /* Pack does not work here, because it sets zero-values for relative times */
    GNUNET_assert (0 ==
                   json_object_set_new (oc->serialize_order.contract,
                                        "auto_refund",
                                        GNUNET_JSON_from_time_rel (
                                          oc->parse_order.auto_refund)));
  }

  oc->phase++;
}


/* ***************** ORDER_PHASE_SET_MAX_FEE **************** */


/**
 * Set @a max_fee in @a oc based on @a max_stefan_fee value if not overridden
 * by @a client_fee.  If neither is set, set the fee to zero using currency
 * from @a brutto.
 *
 * @param[in,out] oc order context
 * @param brutto brutto amount to compute fee for
 * @param client_fee client-given fee override (or invalid)
 * @param max_stefan_fee maximum STEFAN fee of any exchange
 * @param max_fee set to the maximum stefan fee
 */
static void
compute_fee (struct OrderContext *oc,
             const struct TALER_Amount *brutto,
             const struct TALER_Amount *client_fee,
             const struct TALER_Amount *max_stefan_fee,
             struct TALER_Amount *max_fee)
{
  const struct TALER_MERCHANTDB_InstanceSettings *settings
    = &oc->hc->instance->settings;

  if (GNUNET_OK ==
      TALER_amount_is_valid (client_fee))
  {
    *max_fee = *client_fee;
    return;
  }
  if ( (settings->use_stefan) &&
       (NULL != max_stefan_fee) &&
       (GNUNET_OK ==
        TALER_amount_is_valid (max_stefan_fee)) )
  {
    *max_fee = *max_stefan_fee;
    return;
  }
  GNUNET_assert (
    GNUNET_OK ==
    TALER_amount_set_zero (brutto->currency,
                           max_fee));
}


/**
 * Initialize "set_max_fee" in @a oc based on STEFAN value or client
 * preference. Upon success, continue processing in next phase.
 *
 * @param[in,out] oc order context
 */
static void
phase_set_max_fee (struct OrderContext *oc)
{
  switch (oc->parse_order.version)
  {
  case TALER_MERCHANT_CONTRACT_VERSION_0:
    compute_fee (oc,
                 &oc->parse_order.details.v0.brutto,
                 &oc->parse_order.details.v0.max_fee,
                 &oc->set_exchanges.details.v0.max_stefan_fee,
                 &oc->set_max_fee.details.v0.max_fee);
    break;
  case TALER_MERCHANT_CONTRACT_VERSION_1:
    oc->set_max_fee.details.v1.max_fees
      = GNUNET_new_array (oc->parse_choices.choices_len,
                          struct TALER_Amount);
    for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
      compute_fee (oc,
                   &oc->parse_choices.choices[i].amount,
                   &oc->parse_choices.choices[i].max_fee,
                   NULL != oc->set_exchanges.details.v1.max_stefan_fees
                   ? &oc->set_exchanges.details.v1.max_stefan_fees[i]
                   : NULL,
                   &oc->set_max_fee.details.v1.max_fees[i]);
    break;
  default:
    GNUNET_break (0);
    break;
  }
  oc->phase++;
}


/* ***************** ORDER_PHASE_SELECT_WIRE_METHOD **************** */

/**
 * Check that the @a brutto amount is at or below the
 * limits we have for the respective wire method candidate.
 *
 * @param wmc wire method candidate to check
 * @param brutto amount to check
 * @return true if the amount is OK, false if it is too high
 */
static bool
check_limits (struct WireMethodCandidate *wmc,
              const struct TALER_Amount *brutto)
{
  for (unsigned int i = 0; i<wmc->num_total_exchange_limits; i++)
  {
    const struct TALER_Amount *total_exchange_limit
      = &wmc->total_exchange_limits[i];

    if (GNUNET_OK !=
        TALER_amount_cmp_currency (brutto,
                                   total_exchange_limit))
      continue;
    if (1 !=
        TALER_amount_cmp (brutto,
                          total_exchange_limit))
      return true;
  }
  return false;
}


/**
 * Phase to select a wire method that will be acceptable for the order.
 * If none is "perfect" (allows all choices), might jump back to the
 * previous phase to force "/keys" downloads to see if that helps.
 *
 * @param[in,out] oc order context
 */
static void
phase_select_wire_method (struct OrderContext *oc)
{
  const struct TALER_Amount *ea;
  struct WireMethodCandidate *best = NULL;
  unsigned int max_choices = 0;
  unsigned int want_choices = 0;

  for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
       NULL != wmc;
       wmc = wmc->next)
  {
    unsigned int num_choices = 0;

    switch (oc->parse_order.version)
    {
    case TALER_MERCHANT_CONTRACT_VERSION_0:
      want_choices = 1;
      ea = &oc->parse_order.details.v0.brutto;
      if (TALER_amount_is_zero (ea) ||
          check_limits (wmc,
                        ea))
        num_choices++;
      break;
    case TALER_MERCHANT_CONTRACT_VERSION_1:
      want_choices = oc->parse_choices.choices_len;
      for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
      {
        ea = &oc->parse_choices.choices[i].amount;
        if (TALER_amount_is_zero (ea) ||
            check_limits (wmc,
                          ea))
          num_choices++;
      }
      break;
    default:
      GNUNET_assert (0);
    }
    if (num_choices > max_choices)
    {
      best = wmc;
      max_choices = num_choices;
    }
  }

  if ( (want_choices > max_choices) &&
       (oc->set_exchanges.promising_exchange) &&
       (! oc->set_exchanges.forced_reload) )
  {
    oc->set_exchanges.exchange_ok = false;
    /* Not all choices in the contract can work with these
       exchanges, try again with forcing /keys download */
    for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
         NULL != wmc;
         wmc = wmc->next)
    {
      json_array_clear (wmc->exchanges);
      GNUNET_array_grow (wmc->total_exchange_limits,
                         wmc->num_total_exchange_limits,
                         0);
    }
    oc->phase = ORDER_PHASE_SET_EXCHANGES;
    return;
  }

  if ( (NULL == best) &&
       (NULL != oc->parse_request.payment_target) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Cannot create order: lacking suitable exchanges for payment target `%s'\n",
                oc->parse_request.payment_target);
    reply_with_error (
      oc,
      MHD_HTTP_CONFLICT,
      TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGES_FOR_WIRE_METHOD,
      oc->parse_request.payment_target);
    return;
  }

  if (NULL == best)
  {
    /* We actually do not have ANY workable exchange(s) */
    reply_with_error (
      oc,
      MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
      TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS,
      NULL);
    return;
  }

  if (want_choices > max_choices)
  {
    /* Some choices are unpayable */
    GNUNET_log (
      GNUNET_ERROR_TYPE_WARNING,
      "Creating order, but some choices do not work with the selected wire method\n");
  }
  if ( (0 == json_array_size (best->exchanges)) &&
       (oc->add_payment_details.need_exchange) )
  {
    /* We did not find any reasonable exchange */
    GNUNET_log (
      GNUNET_ERROR_TYPE_WARNING,
      "Creating order, but only for choices without payment\n");
  }

  oc->select_wire_method.wm
    = best->wm;
  oc->select_wire_method.exchanges
    = json_incref (best->exchanges);
  oc->phase++;
}


/* ***************** ORDER_PHASE_SET_EXCHANGES **************** */

/**
 * Exchange `/keys` processing is done, resume handling
 * the order.
 *
 * @param[in,out] oc context to resume
 */
static void
resume_with_keys (struct OrderContext *oc)
{
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Resuming order processing after /keys downloads\n");
  GNUNET_assert (GNUNET_YES == oc->suspended);
  GNUNET_CONTAINER_DLL_remove (oc_head,
                               oc_tail,
                               oc);
  oc->suspended = GNUNET_NO;
  MHD_resume_connection (oc->connection);
  TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}


/**
 * Given a @a brutto amount for exchange with @a keys, set the
 * @a stefan_fee. Note that @a stefan_fee is updated to the maximum
 * of the input and the computed fee.
 *
 * @param[in,out] keys exchange keys
 * @param brutto some brutto amount the client is to pay
 * @param[in,out] stefan_fee set to STEFAN fee to be paid by the merchant
 */
static void
compute_stefan_fee (const struct TALER_EXCHANGE_Keys *keys,
                    const struct TALER_Amount *brutto,
                    struct TALER_Amount *stefan_fee)
{
  struct TALER_Amount net;

  if (GNUNET_SYSERR !=
      TALER_EXCHANGE_keys_stefan_b2n (keys,
                                      brutto,
                                      &net))
  {
    struct TALER_Amount fee;

    TALER_EXCHANGE_keys_stefan_round (keys,
                                      &net);
    if (-1 == TALER_amount_cmp (brutto,
                                &net))
    {
      /* brutto < netto! */
      /* => after rounding, there is no real difference */
      net = *brutto;
    }
    GNUNET_assert (0 <=
                   TALER_amount_subtract (&fee,
                                          brutto,
                                          &net));
    if ( (GNUNET_OK !=
          TALER_amount_is_valid (stefan_fee)) ||
         (-1 == TALER_amount_cmp (stefan_fee,
                                  &fee)) )
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Updated STEFAN-based fee to %s\n",
                  TALER_amount2s (&fee));
      *stefan_fee = fee;
    }
  }
}


/**
 * Update MAX STEFAN fees based on @a keys.
 *
 * @param[in,out] oc order context to update
 * @param keys keys to derive STEFAN fees from
 */
static void
update_stefan (struct OrderContext *oc,
               const struct TALER_EXCHANGE_Keys *keys)
{
  switch (oc->parse_order.version)
  {
  case TALER_MERCHANT_CONTRACT_VERSION_0:
    compute_stefan_fee (keys,
                        &oc->parse_order.details.v0.brutto,
                        &oc->set_exchanges.details.v0.max_stefan_fee);
    break;
  case TALER_MERCHANT_CONTRACT_VERSION_1:
    oc->set_exchanges.details.v1.max_stefan_fees
      = GNUNET_new_array (oc->parse_choices.choices_len,
                          struct TALER_Amount);
    for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
      if (0 == strcasecmp (keys->currency,
                           oc->parse_choices.choices[i].amount.currency))
        compute_stefan_fee (keys,
                            &oc->parse_choices.choices[i].amount,
                            &oc->set_exchanges.details.v1.max_stefan_fees[i]);
    break;
  default:
    GNUNET_assert (0);
  }
}


/**
 * Check our KYC status at all exchanges as our current limit is
 * too low and we failed to create an order.
 *
 * @param oc order context
 * @param wmc wire method candidate to notify for
 * @param exchange_url exchange to notify about
 */
static void
notify_kyc_required (const struct OrderContext *oc,
                     const struct WireMethodCandidate *wmc,
                     const char *exchange_url)
{
  struct GNUNET_DB_EventHeaderP es = {
    .size = htons (sizeof (es)),
    .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_RULE_TRIGGERED)
  };
  char *hws;
  char *extra;

  hws = GNUNET_STRINGS_data_to_string_alloc (
    &wmc->wm->h_wire,
    sizeof (wmc->wm->h_wire));

  GNUNET_asprintf (&extra,
                   "%s %s",
                   hws,
                   exchange_url);
  TMH_db->event_notify (TMH_db->cls,
                        &es,
                        extra,
                        strlen (extra) + 1);
  GNUNET_free (extra);
  GNUNET_free (hws);
}


/**
 * Checks the limits that apply for this @a exchange and
 * the @a wmc and if the exchange is acceptable at all, adds it
 * to the list of exchanges for the @a wmc.
 *
 * @param oc context of the order
 * @param exchange internal handle for the exchange
 * @param exchange_url base URL of this exchange
 * @param wmc wire method to evaluate this exchange for
 * @return true if the exchange is acceptable for the contract
 */
static bool
get_acceptable (struct OrderContext *oc,
                const struct TMH_Exchange *exchange,
                const char *exchange_url,
                struct WireMethodCandidate *wmc)
{
  const struct TALER_Amount *max_needed = NULL;
  unsigned int priority = 42; /* make compiler happy */
  json_t *j_exchange;
  enum TMH_ExchangeStatus res;
  struct TALER_Amount max_amount;

  for (unsigned int i = 0; i<oc->add_payment_details.num_max_choice_limits; i++)
  {
    struct TALER_Amount *val = &oc->add_payment_details.max_choice_limits[i];

    if (0 == strcasecmp (val->currency,
                         TMH_EXCHANGES_get_currency (exchange)))
    {
      max_needed = val;
      break;
    }
  }
  if (NULL == max_needed)
  {
    /* exchange currency not relevant for any of our choices, skip it */
    return false;
  }

  max_amount = *max_needed;
  res = TMH_exchange_check_debit (
    oc->hc->instance->settings.id,
    exchange,
    wmc->wm,
    &max_amount);
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Exchange %s evaluated at %d with max %s\n",
              exchange_url,
              res,
              TALER_amount2s (&max_amount));
  if (TALER_amount_is_zero (&max_amount))
  {
    if (! TALER_amount_is_zero (max_needed))
    {
      /* Trigger re-checking the current deposit limit when
       * paying non-zero amount with zero deposit limit */
      notify_kyc_required (oc,
                           wmc,
                           exchange_url);
    }
    /* If deposit is impossible, we don't list the
     * exchange in the contract terms. */
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Exchange %s deposit limit is zero, skipping it\n",
                exchange_url);
    return false;
  }
  switch (res)
  {
  case TMH_ES_OK:
  case TMH_ES_RETRY_OK:
    priority = 1024;   /* high */
    oc->set_exchanges.exchange_ok = true;
    break;
  case TMH_ES_NO_ACC:
    if (oc->set_exchanges.forced_reload)
      priority = 0;   /* fresh negative response */
    else
      priority = 512; /* stale negative response */
    break;
  case TMH_ES_NO_CURR:
    if (oc->set_exchanges.forced_reload)
      priority = 0;   /* fresh negative response */
    else
      priority = 512; /* stale negative response */
    break;
  case TMH_ES_NO_KEYS:
    if (oc->set_exchanges.forced_reload)
      priority = 256;   /* fresh, no accounts yet */
    else
      priority = 768;  /* stale, no accounts yet */
    break;
  case TMH_ES_NO_ACC_RETRY_OK:
    if (oc->set_exchanges.forced_reload)
    {
      priority = 0;   /* fresh negative response */
    }
    else
    {
      oc->set_exchanges.promising_exchange = true;
      priority = 512; /* stale negative response */
    }
    break;
  case TMH_ES_NO_CURR_RETRY_OK:
    if (oc->set_exchanges.forced_reload)
      priority = 0;   /* fresh negative response */
    else
      priority = 512; /* stale negative response */
    break;
  case TMH_ES_NO_KEYS_RETRY_OK:
    if (oc->set_exchanges.forced_reload)
    {
      priority = 256;   /* fresh, no accounts yet */
    }
    else
    {
      oc->set_exchanges.promising_exchange = true;
      priority = 768;  /* stale, no accounts yet */
    }
    break;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Exchange %s deposit limit is %s, adding it!\n",
              exchange_url,
              TALER_amount2s (&max_amount));

  j_exchange = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_string ("url",
                             exchange_url),
    GNUNET_JSON_pack_uint64 ("priority",
                             priority),
    TALER_JSON_pack_amount ("max_contribution",
                            &max_amount),
    GNUNET_JSON_pack_data_auto ("master_pub",
                                TMH_EXCHANGES_get_master_pub (exchange)));
  GNUNET_assert (NULL != j_exchange);
  /* Add exchange to list of exchanges for this wire method
     candidate */
  GNUNET_assert (0 ==
                 json_array_append_new (wmc->exchanges,
                                        j_exchange));
  add_to_currency_vector (&wmc->total_exchange_limits,
                          &wmc->num_total_exchange_limits,
                          &max_amount,
                          max_needed);
  return true;
}


/**
 * Function called with the result of a #TMH_EXCHANGES_keys4exchange()
 * operation.
 *
 * @param cls closure with our `struct RekeyExchange *`
 * @param keys the keys of the exchange
 * @param exchange representation of the exchange
 */
static void
keys_cb (
  void *cls,
  struct TALER_EXCHANGE_Keys *keys,
  struct TMH_Exchange *exchange)
{
  struct RekeyExchange *rx = cls;
  struct OrderContext *oc = rx->oc;
  const struct TALER_MERCHANTDB_InstanceSettings *settings =
    &oc->hc->instance->settings;
  bool applicable = false;

  rx->fo = NULL;
  GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
                               oc->set_exchanges.pending_reload_tail,
                               rx);
  if (NULL == keys)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Failed to download %skeys\n",
                rx->url);
    oc->set_exchanges.promising_exchange = true;
    goto cleanup;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Got response for %skeys\n",
              rx->url);

  /* Evaluate the use of this exchange for each wire method candidate */
  for (unsigned int j = 0; j<keys->accounts_len; j++)
  {
    struct TALER_FullPayto full_payto = keys->accounts[j].fpayto_uri;
    char *wire_method = TALER_payto_get_method (full_payto.full_payto);

    for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
         NULL != wmc;
         wmc = wmc->next)
    {
      if (0 == strcmp (wmc->wm->wire_method,
                       wire_method) )
      {
        applicable |= get_acceptable (oc,
                                      exchange,
                                      rx->url,
                                      wmc);
      }
    }
    GNUNET_free (wire_method);
  }
  if (applicable &&
      settings->use_stefan)
    update_stefan (oc,
                   keys);
cleanup:
  GNUNET_free (rx->url);
  GNUNET_free (rx);
  if (NULL != oc->set_exchanges.pending_reload_head)
    return;
  resume_with_keys (oc);
}


/**
 * Force re-downloading of /keys from @a exchange,
 * we currently have no acceptable exchange, so we
 * should try to get one.
 *
 * @param cls closure with our `struct OrderContext`
 * @param url base URL of the exchange
 * @param exchange internal handle for the exchange
 */
static void
get_exchange_keys (void *cls,
                   const char *url,
                   const struct TMH_Exchange *exchange)
{
  struct OrderContext *oc = cls;
  struct RekeyExchange *rx;

  rx = GNUNET_new (struct RekeyExchange);
  rx->oc = oc;
  rx->url = GNUNET_strdup (url);
  GNUNET_CONTAINER_DLL_insert (oc->set_exchanges.pending_reload_head,
                               oc->set_exchanges.pending_reload_tail,
                               rx);
  if (oc->set_exchanges.forced_reload)
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Forcing download of %skeys\n",
                url);
  rx->fo = TMH_EXCHANGES_keys4exchange (url,
                                        oc->set_exchanges.forced_reload,
                                        &keys_cb,
                                        rx);
}


/**
 * Task run when we are timing out on /keys and will just
 * proceed with what we got.
 *
 * @param cls our `struct OrderContext *` to resume
 */
static void
wakeup_timeout (void *cls)
{
  struct OrderContext *oc = cls;

  oc->set_exchanges.wakeup_task = NULL;
  GNUNET_assert (GNUNET_YES == oc->suspended);
  GNUNET_CONTAINER_DLL_remove (oc_head,
                               oc_tail,
                               oc);
  MHD_resume_connection (oc->connection);
  oc->suspended = GNUNET_NO;
  TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}


/**
 * Set list of acceptable exchanges in @a oc. Upon success, continues
 * processing with add_payment_details().
 *
 * @param[in,out] oc order context
 * @return true to suspend execution
 */
static bool
phase_set_exchanges (struct OrderContext *oc)
{
  if (NULL != oc->set_exchanges.wakeup_task)
  {
    GNUNET_SCHEDULER_cancel (oc->set_exchanges.wakeup_task);
    oc->set_exchanges.wakeup_task = NULL;
  }

  if (! oc->add_payment_details.need_exchange)
  {
    /* Total amount is zero, so we don't actually need exchanges! */
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Order total is zero, no need for exchanges\n");
    oc->select_wire_method.exchanges = json_array ();
    GNUNET_assert (NULL != oc->select_wire_method.exchanges);
    /* Pick first one, doesn't matter as the amount is zero */
    oc->select_wire_method.wm = oc->hc->instance->wm_head;
    oc->phase = ORDER_PHASE_SET_MAX_FEE;
    return false;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Trying to find exchanges\n");
  if (NULL == oc->set_exchanges.pending_reload_head)
  {
    if (! oc->set_exchanges.exchanges_tried)
    {
      oc->set_exchanges.exchanges_tried = true;
      oc->set_exchanges.keys_timeout
        = GNUNET_TIME_relative_to_absolute (MAX_KEYS_WAIT);
      TMH_exchange_get_trusted (&get_exchange_keys,
                                oc);
    }
    else if ( (! oc->set_exchanges.forced_reload) &&
              (oc->set_exchanges.promising_exchange) &&
              (! oc->set_exchanges.exchange_ok) )
    {
      for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
           NULL != wmc;
           wmc = wmc->next)
        GNUNET_break (0 ==
                      json_array_clear (wmc->exchanges));
      /* Try one more time with forcing /keys download */
      oc->set_exchanges.forced_reload = true;
      TMH_exchange_get_trusted (&get_exchange_keys,
                                oc);
    }
  }
  if (GNUNET_TIME_absolute_is_past (oc->set_exchanges.keys_timeout))
  {
    struct RekeyExchange *rx;

    while (NULL != (rx = oc->set_exchanges.pending_reload_head))
    {
      GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
                                   oc->set_exchanges.pending_reload_tail,
                                   rx);
      TMH_EXCHANGES_keys4exchange_cancel (rx->fo);
      GNUNET_free (rx->url);
      GNUNET_free (rx);
    }
  }
  if (NULL != oc->set_exchanges.pending_reload_head)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Still trying to (re)load %skeys\n",
                oc->set_exchanges.pending_reload_head->url);
    oc->set_exchanges.wakeup_task
      = GNUNET_SCHEDULER_add_at (oc->set_exchanges.keys_timeout,
                                 &wakeup_timeout,
                                 oc);
    MHD_suspend_connection (oc->connection);
    oc->suspended = GNUNET_YES;
    GNUNET_CONTAINER_DLL_insert (oc_head,
                                 oc_tail,
                                 oc);
    return true; /* reloads pending */
  }
  oc->phase++;
  return false;
}


/* ***************** ORDER_PHASE_ADD_PAYMENT_DETAILS **************** */

/**
 * Process the @a payment_target and add the details of how the
 * order could be paid to @a order. On success, continue
 * processing with add_payment_fees().
 *
 * @param[in,out] oc order context
 */
static void
phase_add_payment_details (struct OrderContext *oc)
{
  /* First, determine the maximum amounts that could be paid per currency */
  switch (oc->parse_order.version)
  {
  case TALER_MERCHANT_CONTRACT_VERSION_0:
    GNUNET_array_append (oc->add_payment_details.max_choice_limits,
                         oc->add_payment_details.num_max_choice_limits,
                         oc->parse_order.details.v0.brutto);
    if (! TALER_amount_is_zero (
          &oc->parse_order.details.v0.brutto))
    {
      oc->add_payment_details.need_exchange = true;
    }
    break;
  case TALER_MERCHANT_CONTRACT_VERSION_1:
    for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
    {
      const struct TALER_Amount *amount
        = &oc->parse_choices.choices[i].amount;
      bool found = false;

      if (! TALER_amount_is_zero (amount))
      {
        oc->add_payment_details.need_exchange = true;
      }
      for (unsigned int j = 0; j<oc->add_payment_details.num_max_choice_limits;
           j++)
      {
        struct TALER_Amount *mx = &oc->add_payment_details.max_choice_limits[j];
        if (GNUNET_YES ==
            TALER_amount_cmp_currency (mx,
                                       amount))
        {
          TALER_amount_max (mx,
                            mx,
                            amount);
          found = true;
          break;
        }
      }
      if (! found)
      {
        GNUNET_array_append (oc->add_payment_details.max_choice_limits,
                             oc->add_payment_details.num_max_choice_limits,
                             *amount);
      }
    }
    break;
  default:
    GNUNET_assert (0);
  }

  /* Then, create a candidate for each available wire method */
  for (struct TMH_WireMethod *wm = oc->hc->instance->wm_head;
       NULL != wm;
       wm = wm->next)
  {
    struct WireMethodCandidate *wmc;

    /* Locate wire method that has a matching payment target */
    if (! wm->active)
      continue; /* ignore inactive methods */
    if ( (NULL != oc->parse_request.payment_target) &&
         (0 != strcasecmp (oc->parse_request.payment_target,
                           wm->wire_method) ) )
      continue; /* honor client preference */
    wmc = GNUNET_new (struct WireMethodCandidate);
    wmc->wm = wm;
    wmc->exchanges = json_array ();
    GNUNET_assert (NULL != wmc->exchanges);
    GNUNET_CONTAINER_DLL_insert (oc->add_payment_details.wmc_head,
                                 oc->add_payment_details.wmc_tail,
                                 wmc);
  }

  if (NULL == oc->add_payment_details.wmc_head)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "No wire method available for instance '%s'\n",
                oc->hc->instance->settings.id);
    reply_with_error (oc,
                      MHD_HTTP_NOT_FOUND,
                      TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE,
                      oc->parse_request.payment_target);
    return;
  }

  /* next, we'll evaluate available exchanges */
  oc->phase++;
}


/* ***************** ORDER_PHASE_MERGE_INVENTORY **************** */


/**
 * Merge the inventory products into products, querying the
 * database about the details of those products. Upon success,
 * continue processing by calling add_payment_details().
 *
 * @param[in,out] oc order context to process
 */
static void
phase_merge_inventory (struct OrderContext *oc)
{
  /**
   * parse_request.inventory_products => instructions to add products to contract terms
   * parse_order.products => contains products that are not from the backend-managed inventory.
   */
  if (NULL != oc->parse_order.products)
    oc->merge_inventory.products
      = json_deep_copy (oc->parse_order.products);
  else
    oc->merge_inventory.products
      = json_array ();
  /* Populate products from inventory product array and database */
  {
    GNUNET_assert (NULL != oc->merge_inventory.products);
    for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
    {
      struct InventoryProduct *ip
        = &oc->parse_request.inventory_products[i];
      struct TALER_MERCHANTDB_ProductDetails pd;
      enum GNUNET_DB_QueryStatus qs;
      size_t num_categories = 0;
      uint64_t *categories = NULL;

      qs = TMH_db->lookup_product (TMH_db->cls,
                                   oc->hc->instance->settings.id,
                                   ip->product_id,
                                   &pd,
                                   &num_categories,
                                   &categories);
      if (qs <= 0)
      {
        enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
        unsigned int http_status = 0;

        switch (qs)
        {
        case GNUNET_DB_STATUS_HARD_ERROR:
          GNUNET_break (0);
          http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
          ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
          break;
        case GNUNET_DB_STATUS_SOFT_ERROR:
          GNUNET_break (0);
          http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
          ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
          break;
        case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                      "Product %s from order unknown\n",
                      ip->product_id);
          http_status = MHD_HTTP_NOT_FOUND;
          ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
          break;
        case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
          /* case listed to make compilers happy */
          GNUNET_assert (0);
        }
        reply_with_error (oc,
                          http_status,
                          ec,
                          ip->product_id);
        return;
      }
      GNUNET_free (categories);
      oc->parse_order.minimum_age
        = GNUNET_MAX (oc->parse_order.minimum_age,
                      pd.minimum_age);
      {
        const char *eparam;

        if ( (! ip->quantity_missing) &&
             (ip->quantity > (uint64_t) INT64_MAX) )
        {
          GNUNET_break_op (0);
          reply_with_error (oc,
                            MHD_HTTP_BAD_REQUEST,
                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
                            "quantity");
          TALER_MERCHANTDB_product_details_free (&pd);
          return;
        }
        if (GNUNET_OK !=
            TMH_process_quantity_inputs (TMH_VK_QUANTITY,
                                         pd.allow_fractional_quantity,
                                         ip->quantity_missing,
                                         (int64_t) ip->quantity,
                                         ip->unit_quantity_missing,
                                         ip->unit_quantity,
                                         &ip->quantity,
                                         &ip->quantity_frac,
                                         &eparam))
        {
          GNUNET_break_op (0);
          reply_with_error (oc,
                            MHD_HTTP_BAD_REQUEST,
                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
                            eparam);
          TALER_MERCHANTDB_product_details_free (&pd);
          return;
        }
      }
      {
        json_t *p;
        char unit_quantity_buf[64];

        TMH_format_fractional_string (TMH_VK_QUANTITY,
                                      ip->quantity,
                                      ip->quantity_frac,
                                      sizeof (unit_quantity_buf),
                                      unit_quantity_buf);

        p = GNUNET_JSON_PACK (
          GNUNET_JSON_pack_string ("product_name",
                                   pd.product_name),
          GNUNET_JSON_pack_string ("description",
                                   pd.description),
          GNUNET_JSON_pack_object_incref ("description_i18n",
                                          pd.description_i18n),
          GNUNET_JSON_pack_string ("unit",
                                   pd.unit),
          TALER_JSON_pack_amount ("price",
                                  &pd.price),
          GNUNET_JSON_pack_array_incref ("taxes",
                                         pd.taxes),
          GNUNET_JSON_pack_string ("image",
                                   pd.image),
          GNUNET_JSON_pack_uint64 (
            "quantity",
            ip->quantity),
          GNUNET_JSON_pack_string ("unit_quantity",
                                   unit_quantity_buf));
        GNUNET_assert (NULL != p);
        GNUNET_assert (0 ==
                       json_array_append_new (oc->merge_inventory.products,
                                              p));
      }
      TALER_MERCHANTDB_product_details_free (&pd);
    }
  }
  /* check if final product list is well-formed */
  if (! TMH_products_array_valid (oc->merge_inventory.products))
  {
    GNUNET_break_op (0);
    reply_with_error (oc,
                      MHD_HTTP_BAD_REQUEST,
                      TALER_EC_GENERIC_PARAMETER_MALFORMED,
                      "order:products");
    return;
  }
  oc->phase++;
}


/* ***************** ORDER_PHASE_PARSE_CHOICES **************** */

#ifdef HAVE_DONAU_DONAU_SERVICE_H
/**
 * Callback function that is called for each donau instance.
 * It simply adds the provided donau_url to the json.
 *
 * @param cls closure with our `struct TALER_MERCHANT_ContractOutput *`
 * @param donau_url the URL of the donau instance
 */
static void
add_donau_url (void *cls,
               const char *donau_url)
{
  struct TALER_MERCHANT_ContractOutput *output = cls;

  GNUNET_array_append (output->details.donation_receipt.donau_urls,
                       output->details.donation_receipt.donau_urls_len,
                       GNUNET_strdup (donau_url));
}


/**
 * Add the donau output to the contract output.
 *
 * @param oc order context
 * @param output contract output to add donau URLs to
 */
static bool
add_donau_output (struct OrderContext *oc,
                  struct TALER_MERCHANT_ContractOutput *output)
{
  enum GNUNET_DB_QueryStatus qs;

  qs = TMH_db->select_donau_instances_filtered (
    TMH_db->cls,
    output->details.donation_receipt.amount.currency,
    &add_donau_url,
    output);
  if (qs < 0)
  {
    GNUNET_break (0);
    reply_with_error (oc,
                      MHD_HTTP_INTERNAL_SERVER_ERROR,
                      TALER_EC_GENERIC_DB_FETCH_FAILED,
                      "donau url parsing db call");
    for (unsigned int i = 0;
         i < output->details.donation_receipt.donau_urls_len;
         i++)
      GNUNET_free (output->details.donation_receipt.donau_urls[i]);
    GNUNET_array_grow (output->details.donation_receipt.donau_urls,
                       output->details.donation_receipt.donau_urls_len,
                       0);
    return false;
  }
  return true;
}


#endif

/**
 * Parse contract choices. Upon success, continue
 * processing with merge_inventory().
 *
 * @param[in,out] oc order context
 */
static void
phase_parse_choices (struct OrderContext *oc)
{
  const json_t *jchoices;

  switch (oc->parse_order.version)
  {
  case TALER_MERCHANT_CONTRACT_VERSION_0:
    oc->phase++;
    return;
  case TALER_MERCHANT_CONTRACT_VERSION_1:
    /* handle below */
    break;
  default:
    GNUNET_assert (0);
  }

  jchoices = oc->parse_order.details.v1.choices;

  if (! json_is_array (jchoices))
    GNUNET_assert (0);
  if (0 == json_array_size (jchoices))
  {
    reply_with_error (oc,
                      MHD_HTTP_BAD_REQUEST,
                      TALER_EC_GENERIC_PARAMETER_MALFORMED,
                      "choices");
    return;
  }
  GNUNET_array_grow (oc->parse_choices.choices,
                     oc->parse_choices.choices_len,
                     json_array_size (jchoices));
  for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
  {
    struct TALER_MERCHANT_ContractChoice *choice
      = &oc->parse_choices.choices[i];
    const char *error_name;
    unsigned int error_line;
    const json_t *jinputs;
    const json_t *joutputs;
    bool no_fee;
    struct GNUNET_JSON_Specification spec[] = {
      TALER_JSON_spec_amount_any ("amount",
                                  &choice->amount),
      GNUNET_JSON_spec_mark_optional (
        TALER_JSON_spec_amount_any ("max_fee",
                                    &choice->max_fee),
        &no_fee),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_string_copy ("description",
                                      &choice->description),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_object_copy ("description_i18n",
                                      &choice->description_i18n),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_array_const ("inputs",
                                      &jinputs),
        NULL),
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_array_const ("outputs",
                                      &joutputs),
        NULL),
      GNUNET_JSON_spec_end ()
    };
    enum GNUNET_GenericReturnValue ret;

    ret = GNUNET_JSON_parse (json_array_get (jchoices,
                                             i),
                             spec,
                             &error_name,
                             &error_line);
    if (GNUNET_OK != ret)
    {
      GNUNET_break_op (0);
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Choice parsing failed: %s:%u\n",
                  error_name,
                  error_line);
      reply_with_error (oc,
                        MHD_HTTP_BAD_REQUEST,
                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
                        "choice");
      return;
    }
    if ( (! no_fee) &&
         (GNUNET_OK !=
          TALER_amount_cmp_currency (&choice->amount,
                                     &choice->max_fee)) )
    {
      GNUNET_break_op (0);
      GNUNET_JSON_parse_free (spec);
      reply_with_error (oc,
                        MHD_HTTP_BAD_REQUEST,
                        TALER_EC_GENERIC_CURRENCY_MISMATCH,
                        "different currencies used for 'max_fee' and 'amount' currency");
      return;
    }

    if (! TMH_test_exchange_configured_for_currency (
          choice->amount.currency))
    {
      GNUNET_break_op (0);
      GNUNET_JSON_parse_free (spec);
      reply_with_error (oc,
                        MHD_HTTP_CONFLICT,
                        TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGE_FOR_CURRENCY,
                        choice->amount.currency);
      return;
    }

    if (NULL != jinputs)
    {
      const json_t *jinput;
      size_t idx;
      json_array_foreach ((json_t *) jinputs, idx, jinput)
      {
        struct TALER_MERCHANT_ContractInput input = {
          .details.token.count = 1
        };

        if (GNUNET_OK !=
            TALER_MERCHANT_parse_choice_input ((json_t *) jinput,
                                               &input,
                                               idx,
                                               true))
        {
          GNUNET_break_op (0);
          reply_with_error (oc,
                            MHD_HTTP_BAD_REQUEST,
                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
                            "input");
          return;
        }

        switch (input.type)
        {
        case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
          GNUNET_assert (0);
          break;
        case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
          /* Ignore inputs tokens with 'count' field set to 0 */
          if (0 == input.details.token.count)
            continue;

          if (GNUNET_OK !=
              add_input_token_family (oc,
                                      input.details.token.token_family_slug))

          {
            GNUNET_break_op (0);
            reply_with_error (oc,
                              MHD_HTTP_BAD_REQUEST,
                              TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN,
                              input.details.token.token_family_slug);
            return;
          }

          GNUNET_array_append (choice->inputs,
                               choice->inputs_len,
                               input);
          continue;
        }
        GNUNET_assert (0);
      }
    }

    if (NULL != joutputs)
    {
      const json_t *joutput;
      size_t idx;
      json_array_foreach ((json_t *) joutputs, idx, joutput)
      {
        struct TALER_MERCHANT_ContractOutput output = {
          .details.token.count = 1
        };

        if (GNUNET_OK !=
            TALER_MERCHANT_parse_choice_output ((json_t *) joutput,
                                                &output,
                                                idx,
                                                true))
        {
          reply_with_error (oc,
                            MHD_HTTP_BAD_REQUEST,
                            TALER_EC_GENERIC_PARAMETER_MALFORMED,
                            "output");
          return;
        }

        switch (output.type)
        {
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
          GNUNET_assert (0);
          break;
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
#ifdef HAVE_DONAU_DONAU_SERVICE_H
          output.details.donation_receipt.amount = choice->amount;
          if (! add_donau_output (oc,
                                  &output))
          {
            GNUNET_break (0);
            return;
          }
          GNUNET_array_append (choice->outputs,
                               choice->outputs_len,
                               output);
#endif
          continue;
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
          /* Ignore inputs tokens with 'count' field set to 0 */
          if (0 == output.details.token.count)
            continue;

          if (0 == output.details.token.valid_at.abs_time.abs_value_us)
            output.details.token.valid_at
              = GNUNET_TIME_timestamp_get ();
          if (GNUNET_OK !=
              add_output_token_family (oc,
                                       output.details.token.token_family_slug,
                                       output.details.token.valid_at,
                                       &output.details.token.key_index))

          {
            /* note: reply_with_error() was already called */
            GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                        "Could not handle output token family `%s'\n",
                        output.details.token.token_family_slug);
            return;
          }

          GNUNET_array_append (choice->outputs,
                               choice->outputs_len,
                               output);
          continue;
        }
        GNUNET_assert (0);
      }
    }
  }
  oc->phase++;
}


/* ***************** ORDER_PHASE_PARSE_ORDER **************** */


/**
 * Parse the order field of the request. Upon success, continue
 * processing with parse_choices().
 *
 * @param[in,out] oc order context
 */
static void
phase_parse_order (struct OrderContext *oc)
{
  const struct TALER_MERCHANTDB_InstanceSettings *settings =
    &oc->hc->instance->settings;
  const char *merchant_base_url = NULL;
  uint64_t version = 0;
  const json_t *jmerchant = NULL;
  const char *order_id = NULL;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_uint64 ("version",
                               &version),
      NULL),
    GNUNET_JSON_spec_string ("summary",
                             &oc->parse_order.summary),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("products",
                                    &oc->parse_order.products),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("summary_i18n",
                                     &oc->parse_order.summary_i18n),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("order_id",
                               &order_id),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("fulfillment_message",
                               &oc->parse_order.fulfillment_message),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("fulfillment_message_i18n",
                                     &oc->parse_order.fulfillment_message_i18n),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("fulfillment_url",
                               &oc->parse_order.fulfillment_url),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("public_reorder_url",
                               &oc->parse_order.public_reorder_url),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      TALER_JSON_spec_web_url ("merchant_base_url",
                               &merchant_base_url),
      NULL),
    /* For sanity check, this field must NOT be present */
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("merchant",
                                     &jmerchant),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_timestamp ("timestamp",
                                  &oc->parse_order.timestamp),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_timestamp ("refund_deadline",
                                  &oc->parse_order.refund_deadline),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_timestamp ("pay_deadline",
                                  &oc->parse_order.pay_deadline),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
                                  &oc->parse_order.wire_deadline),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("delivery_location",
                                     &oc->parse_order.delivery_location),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_timestamp ("delivery_date",
                                  &oc->parse_order.delivery_date),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_uint32 ("minimum_age",
                               &oc->parse_order.minimum_age),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_relative_time ("auto_refund",
                                      &oc->parse_order.auto_refund),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("extra",
                                     &oc->parse_order.extra),
      NULL),
    GNUNET_JSON_spec_end ()
  };
  enum GNUNET_GenericReturnValue ret;
  bool computed_refund_deadline;

  oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
  oc->parse_order.wire_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
  ret = TALER_MHD_parse_json_data (oc->connection,
                                   oc->parse_request.order,
                                   spec);
  if (GNUNET_OK != ret)
  {
    GNUNET_break_op (0);
    finalize_order2 (oc,
                     ret);
    return;
  }
  if (NULL != order_id)
  {
    size_t len = strlen (order_id);

    for (size_t i = 0; i<len; i++)
    {
      char c = order_id[i];

      if (! ( ( (c >= 'A') &&
                (c <= 'Z') ) ||
              ( (c >= 'a') &&
                (c <= 'z') ) ||
              ( (c >= '0') &&
                (c <= '9') ) ||
              (c == '-') ||
              (c == '_') ||
              (c == '.') ||
              (c == ':') ) )
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Invalid character `%c' in order ID `%s'\n",
                    c,
                    order_id);
        reply_with_error (oc,
                          MHD_HTTP_BAD_REQUEST,
                          TALER_EC_GENERIC_CURRENCY_MISMATCH,
                          "Invalid character in order_id");
        return;
      }
    }
  }
  switch (version)
  {
  case 0:
    {
      bool no_fee;
      const json_t *choices = NULL;
      struct GNUNET_JSON_Specification specv0[] = {
        TALER_JSON_spec_amount_any (
          "amount",
          &oc->parse_order.details.v0.brutto),
        GNUNET_JSON_spec_mark_optional (
          TALER_JSON_spec_amount_any (
            "max_fee",
            &oc->parse_order.details.v0.max_fee),
          &no_fee),
        /* for sanity check, must be *absent*! */
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_array_const ("choices",
                                        &choices),
          NULL),
        GNUNET_JSON_spec_end ()
      };

      ret = TALER_MHD_parse_json_data (oc->connection,
                                       oc->parse_request.order,
                                       specv0);
      if (GNUNET_OK != ret)
      {
        GNUNET_break_op (0);
        finalize_order2 (oc,
                         ret);
        return;
      }
      if ( (! no_fee) &&
           (GNUNET_OK !=
            TALER_amount_cmp_currency (&oc->parse_order.details.v0.brutto,
                                       &oc->parse_order.details.v0.max_fee)) )
      {
        GNUNET_break_op (0);
        GNUNET_JSON_parse_free (spec);
        reply_with_error (oc,
                          MHD_HTTP_BAD_REQUEST,
                          TALER_EC_GENERIC_CURRENCY_MISMATCH,
                          "different currencies used for 'max_fee' and 'amount' currency");
        return;
      }
      if (! TMH_test_exchange_configured_for_currency (
            oc->parse_order.details.v0.brutto.currency))
      {
        GNUNET_break_op (0);
        GNUNET_JSON_parse_free (spec);
        reply_with_error (oc,
                          MHD_HTTP_CONFLICT,
                          TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGE_FOR_CURRENCY,
                          oc->parse_order.details.v0.brutto.currency);
        return;
      }
      if (NULL != choices)
      {
        GNUNET_break_op (0);
        GNUNET_JSON_parse_free (spec);
        reply_with_error (oc,
                          MHD_HTTP_BAD_REQUEST,
                          TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR,
                          "choices array must be null for v0 contracts");
        return;
      }
      oc->parse_order.version = TALER_MERCHANT_CONTRACT_VERSION_0;
      break;
    }
  case 1:
    {
      struct GNUNET_JSON_Specification specv1[] = {
        GNUNET_JSON_spec_array_const (
          "choices",
          &oc->parse_order.details.v1.choices),
        GNUNET_JSON_spec_end ()
      };

      ret = TALER_MHD_parse_json_data (oc->connection,
                                       oc->parse_request.order,
                                       specv1);
      if (GNUNET_OK != ret)
      {
        GNUNET_break_op (0);
        finalize_order2 (oc,
                         ret);
        return;
      }
      oc->parse_order.version = TALER_MERCHANT_CONTRACT_VERSION_1;
      break;
    }
  default:
    GNUNET_break_op (0);
    GNUNET_JSON_parse_free (spec);
    reply_with_error (oc,
                      MHD_HTTP_BAD_REQUEST,
                      TALER_EC_GENERIC_VERSION_MALFORMED,
                      "invalid version specified in order, supported are null, '0' or '1'");
    return;
  }

  /* Add order_id if it doesn't exist. */
  if (NULL != order_id)
  {
    oc->parse_order.order_id = GNUNET_strdup (order_id);
  }
  else
  {
    char buf[256];
    time_t timer;
    struct tm *tm_info;
    size_t off;
    uint64_t rand;
    char *last;

    time (&timer);
    tm_info = localtime (&timer);
    if (NULL == tm_info)
    {
      GNUNET_JSON_parse_free (spec);
      reply_with_error (
        oc,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_LOCALTIME,
        NULL);
      return;
    }
    off = strftime (buf,
                    sizeof (buf) - 1,
                    "%Y.%j",
                    tm_info);
    /* Check for error state of strftime */
    GNUNET_assert (0 != off);
    buf[off++] = '-';
    rand = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
                                     UINT64_MAX);
    last = GNUNET_STRINGS_data_to_string (&rand,
                                          sizeof (uint64_t),
                                          &buf[off],
                                          sizeof (buf) - off);
    GNUNET_assert (NULL != last);
    *last = '\0';

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Assigning order ID `%s' server-side\n",
                buf);

    oc->parse_order.order_id = GNUNET_strdup (buf);
    GNUNET_assert (NULL != oc->parse_order.order_id);
  }

  /* Patch fulfillment URL with order_id (implements #6467). */
  if (NULL != oc->parse_order.fulfillment_url)
  {
    const char *pos;

    pos = strstr (oc->parse_order.fulfillment_url,
                  "${ORDER_ID}");
    if (NULL != pos)
    {
      /* replace ${ORDER_ID} with the real order_id */
      char *nurl;

      /* We only allow one placeholder */
      if (strstr (pos + strlen ("${ORDER_ID}"),
                  "${ORDER_ID}"))
      {
        GNUNET_break_op (0);
        reply_with_error (oc,
                          MHD_HTTP_BAD_REQUEST,
                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
                          "fulfillment_url");
        return;
      }

      GNUNET_asprintf (&nurl,
                       "%.*s%s%s",
                       /* first output URL until ${ORDER_ID} */
                       (int) (pos - oc->parse_order.fulfillment_url),
                       oc->parse_order.fulfillment_url,
                       /* replace ${ORDER_ID} with the right order_id */
                       oc->parse_order.order_id,
                       /* append rest of original URL */
                       pos + strlen ("${ORDER_ID}"));

      oc->parse_order.fulfillment_url = GNUNET_strdup (nurl);

      GNUNET_free (nurl);
    }
  }

  if ( (GNUNET_TIME_absolute_is_zero (oc->parse_order.pay_deadline.abs_time)) ||
       (GNUNET_TIME_absolute_is_never (oc->parse_order.pay_deadline.abs_time)) )
  {
    oc->parse_order.pay_deadline = GNUNET_TIME_relative_to_timestamp (
      settings->default_pay_delay);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Pay deadline was zero (or never), setting to %s\n",
                GNUNET_TIME_timestamp2s (oc->parse_order.pay_deadline));
  }
  else if (GNUNET_TIME_absolute_is_past (oc->parse_order.pay_deadline.abs_time))
  {
    GNUNET_break_op (0);
    reply_with_error (
      oc,
      MHD_HTTP_BAD_REQUEST,
      TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PAY_DEADLINE_IN_PAST,
      NULL);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Pay deadline is %s\n",
              GNUNET_TIME_timestamp2s (oc->parse_order.pay_deadline));

  /* Check soundness of refund deadline, and that a timestamp
   * is actually present.  */
  {
    struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();

    /* Add timestamp if it doesn't exist (or is zero) */
    if (GNUNET_TIME_absolute_is_zero (oc->parse_order.timestamp.abs_time))
    {
      oc->parse_order.timestamp = now;
    }

    /* If no refund_deadline given, set one based on refund_delay.  */
    if (GNUNET_TIME_absolute_is_never (
          oc->parse_order.refund_deadline.abs_time))
    {
      if (GNUNET_TIME_relative_is_zero (oc->parse_request.refund_delay))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "Refund delay is zero, no refunds are possible for this order\n");
        oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
      }
      else
      {
        computed_refund_deadline = true;
        oc->parse_order.refund_deadline
          = GNUNET_TIME_absolute_to_timestamp (
              GNUNET_TIME_absolute_add (oc->parse_order.pay_deadline.abs_time,
                                        oc->parse_request.refund_delay));
      }
    }

    if ( (! GNUNET_TIME_absolute_is_zero (
            oc->parse_order.delivery_date.abs_time)) &&
         (GNUNET_TIME_absolute_is_past (
            oc->parse_order.delivery_date.abs_time)) )
    {
      GNUNET_break_op (0);
      reply_with_error (
        oc,
        MHD_HTTP_BAD_REQUEST,
        TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_DELIVERY_DATE_IN_PAST,
        NULL);
      return;
    }
  }

  if ( (! GNUNET_TIME_absolute_is_zero (
          oc->parse_order.refund_deadline.abs_time)) &&
       (GNUNET_TIME_absolute_is_past (
          oc->parse_order.refund_deadline.abs_time)) )
  {
    GNUNET_break_op (0);
    reply_with_error (
      oc,
      MHD_HTTP_BAD_REQUEST,
      TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_DEADLINE_IN_PAST,
      NULL);
    return;
  }

  if (GNUNET_TIME_absolute_is_never (oc->parse_order.wire_deadline.abs_time))
  {
    struct GNUNET_TIME_Absolute start;

    start = GNUNET_TIME_absolute_max (
      oc->parse_order.refund_deadline.abs_time,
      oc->parse_order.pay_deadline.abs_time);
    oc->parse_order.wire_deadline
      = GNUNET_TIME_absolute_to_timestamp (
          GNUNET_TIME_round_up (
            GNUNET_TIME_absolute_add (
              start,
              settings->default_wire_transfer_delay),
            settings->default_wire_transfer_rounding_interval));
    if (GNUNET_TIME_absolute_is_never (
          oc->parse_order.wire_deadline.abs_time))
    {
      GNUNET_break_op (0);
      reply_with_error (
        oc,
        MHD_HTTP_BAD_REQUEST,
        TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_WIRE_DEADLINE_IS_NEVER,
        "order:wire_transfer_deadline");
      return;
    }
  }
  else if (computed_refund_deadline)
  {
    /* if we computed the refund_deadline from default settings
       and did have a configured wire_deadline, make sure that
       the refund_deadline is at or below the wire_deadline. */
    oc->parse_order.refund_deadline
      = GNUNET_TIME_timestamp_min (oc->parse_order.refund_deadline,
                                   oc->parse_order.wire_deadline);
  }
  if (GNUNET_TIME_timestamp_cmp (oc->parse_order.wire_deadline,
                                 <,
                                 oc->parse_order.refund_deadline))
  {
    GNUNET_break_op (0);
    reply_with_error (
      oc,
      MHD_HTTP_BAD_REQUEST,
      TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE,
      "order:wire_transfer_deadline;order:refund_deadline");
    return;
  }

  if (NULL != merchant_base_url)
  {
    if (('\0' == *merchant_base_url) ||
        ('/' != merchant_base_url[strlen (merchant_base_url) - 1]))
    {
      GNUNET_break_op (0);
      reply_with_error (
        oc,
        MHD_HTTP_BAD_REQUEST,
        TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
        "merchant_base_url is not valid");
      return;
    }
    oc->parse_order.merchant_base_url
      = GNUNET_strdup (merchant_base_url);
  }
  else
  {
    char *url;

    url = make_merchant_base_url (oc->connection,
                                  settings->id);
    if (NULL == url)
    {
      GNUNET_break_op (0);
      reply_with_error (
        oc,
        MHD_HTTP_BAD_REQUEST,
        TALER_EC_GENERIC_PARAMETER_MISSING,
        "order:merchant_base_url");
      return;
    }
    oc->parse_order.merchant_base_url = url;
  }

  if ( (NULL != oc->parse_order.products) &&
       (! TMH_products_array_valid (oc->parse_order.products)) )
  {
    GNUNET_break_op (0);
    reply_with_error (
      oc,
      MHD_HTTP_BAD_REQUEST,
      TALER_EC_GENERIC_PARAMETER_MALFORMED,
      "order.products");
    return;
  }

  /* Merchant information must not already be present */
  if (NULL != jmerchant)
  {
    GNUNET_break_op (0);
    reply_with_error (
      oc,
      MHD_HTTP_BAD_REQUEST,
      TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
      "'merchant' field already set, but must be provided by backend");
    return;
  }

  if ( (NULL != oc->parse_order.delivery_location) &&
       (! TMH_location_object_valid (oc->parse_order.delivery_location)) )
  {
    GNUNET_break_op (0);
    reply_with_error (oc,
                      MHD_HTTP_BAD_REQUEST,
                      TALER_EC_GENERIC_PARAMETER_MALFORMED,
                      "delivery_location");
    return;
  }

  oc->phase++;
}


/* ***************** ORDER_PHASE_PARSE_REQUEST **************** */

/**
 * Parse the client request. Upon success,
 * continue processing by calling parse_order().
 *
 * @param[in,out] oc order context to process
 */
static void
phase_parse_request (struct OrderContext *oc)
{
  const json_t *ip = NULL;
  const json_t *uuid = NULL;
  const char *otp_id = NULL;
  bool create_token = true; /* default */
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_json ("order",
                           &oc->parse_request.order),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_relative_time ("refund_delay",
                                      &oc->parse_request.refund_delay),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("payment_target",
                               &oc->parse_request.payment_target),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("inventory_products",
                                    &ip),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("session_id",
                               &oc->parse_request.session_id),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("lock_uuids",
                                    &uuid),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_bool ("create_token",
                             &create_token),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("otp_id",
                               &otp_id),
      NULL),
    GNUNET_JSON_spec_end ()
  };
  enum GNUNET_GenericReturnValue ret;

  oc->parse_request.refund_delay
    = oc->hc->instance->settings.default_refund_delay;
  ret = TALER_MHD_parse_json_data (oc->connection,
                                   oc->hc->request_body,
                                   spec);
  if (GNUNET_OK != ret)
  {
    GNUNET_break_op (0);
    finalize_order2 (oc,
                     ret);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Refund delay is %s\n",
              GNUNET_TIME_relative2s (oc->parse_request.refund_delay,
                                      false));
  TMH_db->expire_locks (TMH_db->cls);
  if (NULL != otp_id)
  {
    struct TALER_MERCHANTDB_OtpDeviceDetails td;
    enum GNUNET_DB_QueryStatus qs;

    memset (&td,
            0,
            sizeof (td));
    qs = TMH_db->select_otp (TMH_db->cls,
                             oc->hc->instance->settings.id,
                             otp_id,
                             &td);
    switch (qs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
      GNUNET_break (0);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_DB_FETCH_FAILED,
                        "select_otp");
      return;
    case GNUNET_DB_STATUS_SOFT_ERROR:
      GNUNET_break (0);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
                        "select_otp");
      return;
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      reply_with_error (oc,
                        MHD_HTTP_NOT_FOUND,
                        TALER_EC_MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN,
                        otp_id);
      return;
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      break;
    }
    oc->parse_request.pos_key = td.otp_key;
    oc->parse_request.pos_algorithm = td.otp_algorithm;
    GNUNET_free (td.otp_description);
  }
  if (create_token)
  {
    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                                &oc->parse_request.claim_token,
                                sizeof (oc->parse_request.claim_token));
  }
  /* Compute h_post_data (for idempotency check) */
  {
    char *req_body_enc;

    /* Dump normalized JSON to string. */
    if (NULL == (req_body_enc
                   = json_dumps (oc->hc->request_body,
                                 JSON_ENCODE_ANY
                                 | JSON_COMPACT
                                 | JSON_SORT_KEYS)))
    {
      GNUNET_break (0);
      GNUNET_JSON_parse_free (spec);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_ALLOCATION_FAILURE,
                        "request body normalization for hashing");
      return;
    }
    GNUNET_CRYPTO_hash (req_body_enc,
                        strlen (req_body_enc),
                        &oc->parse_request.h_post_data.hash);
    GNUNET_free (req_body_enc);
  }

  /* parse the inventory_products (optionally given) */
  if (NULL != ip)
  {
    unsigned int ipl = (unsigned int) json_array_size (ip);

    if ( (json_array_size (ip) != (size_t) ipl) ||
         (ipl > MAX_PRODUCTS) )
    {
      GNUNET_break (0);
      GNUNET_JSON_parse_free (spec);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_ALLOCATION_FAILURE,
                        "inventory products too long");
      return;
    }
    GNUNET_array_grow (oc->parse_request.inventory_products,
                       oc->parse_request.inventory_products_length,
                       (unsigned int) json_array_size (ip));
    for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
    {
      struct InventoryProduct *ipr = &oc->parse_request.inventory_products[i];
      const char *error_name;
      unsigned int error_line;
      struct GNUNET_JSON_Specification ispec[] = {
        GNUNET_JSON_spec_string ("product_id",
                                 &ipr->product_id),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_uint64 ("quantity",
                                   &ipr->quantity),
          &ipr->quantity_missing),
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_string ("unit_quantity",
                                   &ipr->unit_quantity),
          &ipr->unit_quantity_missing),
        GNUNET_JSON_spec_end ()
      };

      ret = GNUNET_JSON_parse (json_array_get (ip,
                                               i),
                               ispec,
                               &error_name,
                               &error_line);
      if (GNUNET_OK != ret)
      {
        GNUNET_break_op (0);
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Product parsing failed at #%u: %s:%u\n",
                    i,
                    error_name,
                    error_line);
        reply_with_error (oc,
                          MHD_HTTP_BAD_REQUEST,
                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
                          "inventory_products");
        return;
      }
      if (ipr->quantity_missing && ipr->unit_quantity_missing)
      {
        ipr->quantity = 1;
        ipr->quantity_missing = false;
      }
    }
  }

  /* parse the lock_uuids (optionally given) */
  if (NULL != uuid)
  {
    GNUNET_array_grow (oc->parse_request.uuids,
                       oc->parse_request.uuids_length,
                       json_array_size (uuid));
    for (unsigned int i = 0; i<oc->parse_request.uuids_length; i++)
    {
      json_t *ui = json_array_get (uuid,
                                   i);

      if (! json_is_string (ui))
      {
        GNUNET_break_op (0);
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "UUID parsing failed at #%u\n",
                    i);
        reply_with_error (oc,
                          MHD_HTTP_BAD_REQUEST,
                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
                          "lock_uuids");
        return;
      }
      TMH_uuid_from_string (json_string_value (ui),
                            &oc->parse_request.uuids[i]);
    }
  }
  oc->phase++;
}


/* ***************** Main handler **************** */


MHD_RESULT
TMH_private_post_orders (
  const struct TMH_RequestHandler *rh,
  struct MHD_Connection *connection,
  struct TMH_HandlerContext *hc)
{
  struct OrderContext *oc = hc->ctx;

  if (NULL == oc)
  {
    oc = GNUNET_new (struct OrderContext);
    hc->ctx = oc;
    hc->cc = &clean_order;
    oc->connection = connection;
    oc->hc = hc;
  }
  while (1)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Processing order in phase %d\n",
                oc->phase);
    switch (oc->phase)
    {
    case ORDER_PHASE_PARSE_REQUEST:
      phase_parse_request (oc);
      break;
    case ORDER_PHASE_PARSE_ORDER:
      phase_parse_order (oc);
      break;
    case ORDER_PHASE_PARSE_CHOICES:
      phase_parse_choices (oc);
      break;
    case ORDER_PHASE_MERGE_INVENTORY:
      phase_merge_inventory (oc);
      break;
    case ORDER_PHASE_ADD_PAYMENT_DETAILS:
      phase_add_payment_details (oc);
      break;
    case ORDER_PHASE_SET_EXCHANGES:
      if (phase_set_exchanges (oc))
        return MHD_YES;
      break;
    case ORDER_PHASE_SELECT_WIRE_METHOD:
      phase_select_wire_method (oc);
      break;
    case ORDER_PHASE_SET_MAX_FEE:
      phase_set_max_fee (oc);
      break;
    case ORDER_PHASE_SERIALIZE_ORDER:
      phase_serialize_order (oc);
      break;
    case ORDER_PHASE_CHECK_CONTRACT:
      phase_check_contract (oc);
      break;
    case ORDER_PHASE_SALT_FORGETTABLE:
      phase_salt_forgettable (oc);
      break;
    case ORDER_PHASE_EXECUTE_ORDER:
      phase_execute_order (oc);
      break;
    case ORDER_PHASE_FINISHED_MHD_YES:
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Finished processing order (1)\n");
      return MHD_YES;
    case ORDER_PHASE_FINISHED_MHD_NO:
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Finished processing order (0)\n");
      return MHD_NO;
    }
  }
}


/* end of taler-merchant-httpd_private-post-orders.c */
