/*
  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_post-private-orders.c
 * @brief the POST /orders handler
 * @author Christian Grothoff
 * @author Marcello Stanisci
 * @author Christian Blättler
 */
#include "taler/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/taler_merchant_util.h>
#include <time.h>
#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_post-private-orders.h"
#include "taler-merchant-httpd_get-exchanges.h"
#include "taler-merchant-httpd_contract.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_get-private-orders.h"

#include "taler/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;

  /**
   * Money pot associated with the product. 0 for none.
   */
  uint64_t product_money_pot;

};


/**
 * 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;

  /**
   * Set 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_AmountSet 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;

    /**
     * Length of the @e products array.
     */
    size_t products_len;

    /**
     * Array of products that are being sold.
     */
    struct TALER_MERCHANT_ProductSold *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;

    /**
     * Money pot to increment for whatever order payment amount
     * is not yet assigned to a pot via the Product.
     */
    uint64_t order_default_money_pot;

    /**
     * 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;

        /**
         * Tip included by the customer (part of the total amount).
         */
        struct TALER_Amount tip;

        /**
         * True if @e tip was not provided.
         */
        bool no_tip;

        /**
         * 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;

    /**
     * Array of reasons why a particular exchange may be
     * limited or not be eligible.
     */
    json_t *exchange_rejections;

    /**
     * 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);
  }
}


/**
 * 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);
  TALER_amount_set_free (&wmc->total_exchange_limits);
  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;
  }
  if (NULL != oc->set_exchanges.exchange_rejections)
  {
    json_decref (oc->set_exchanges.exchange_rejections);
    oc->set_exchanges.exchange_rejections = 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 (size_t i = 0; i<oc->parse_order.products_len; i++)
  {
    TALER_MERCHANT_product_sold_free (&oc->parse_order.products[i]);
  }
  GNUNET_free (oc->parse_order.products);
  oc->parse_order.products_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..TALER_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) + TALER_MERCHANT_UNIT_FRAC_BASE - 1)
                     / TALER_MERCHANT_UNIT_FRAC_BASE;
    value -= borrow;
    frac += borrow * (int64_t) TALER_MERCHANT_UNIT_FRAC_BASE;
  }
  else if (frac >= TALER_MERCHANT_UNIT_FRAC_BASE)
  {
    int64_t carry = frac / TALER_MERCHANT_UNIT_FRAC_BASE;
    value += carry;
    frac -= carry * (int64_t) TALER_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! */
}


/**
 * The request was successful, generate the #MHD_HTTP_OK response.
 *
 * @param[in,out] oc context to update
 * @param claim_token claim token to use, NULL if none
 */
static void
yield_success_response (struct OrderContext *oc,
                        const struct TALER_ClaimTokenP *claim_token)
{
  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_auto (
        "token",
        claim_token)));
  finalize_order (oc,
                  ret);
}


/**
 * 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)
  {
    yield_success_response (oc,
                            GNUNET_is_zero (&oc->execute_order.token)
                            ? NULL
                            : &oc->execute_order.token);
    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);
      TALER_MERCHANT_vk_format_fractional_string (
        TALER_MERCHANT_VK_QUANTITY,
        ip->quantity,
        ip->quantity_frac,
        sizeof (requested_quantity_buf),
        requested_quantity_buf);
      TALER_MERCHANT_vk_format_fractional_string (
        TALER_MERCHANT_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");
  yield_success_response (oc,
                          GNUNET_is_zero (&oc->parse_request.claim_token)
                          ? NULL
                          : &oc->parse_request.claim_token);
}


/* ***************** 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_allow_null (
      GNUNET_JSON_pack_uint64 ("default_money_pot",
                               oc->parse_order.order_default_money_pot)),
    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),
        GNUNET_JSON_pack_allow_null (
          TALER_JSON_pack_amount ("tip",
                                  oc->parse_order.details.v0.no_tip
                                  ? NULL
                                  : &oc->parse_order.details.v0.tip)),
        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 **************** */

/**
 * 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) ||
          TALER_amount_set_test_above (&wmc->total_exchange_limits,
                                       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) ||
            TALER_amount_set_test_above (&wmc->total_exchange_limits,
                                         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);
      TALER_amount_set_free (&wmc->total_exchange_limits);
    }
    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)
  {
    MHD_RESULT mret;

    /* We actually do not have ANY workable exchange(s) */
    mret = TALER_MHD_reply_json_steal (
      oc->connection,
      GNUNET_JSON_PACK (
        TALER_JSON_pack_ec (
          TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS),
        GNUNET_JSON_pack_array_incref (
          "exchange_rejections",
          oc->set_exchanges.exchange_rejections)),
      MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS);
    finalize_order (oc,
                    mret);
    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);
}


/**
 * Add a reason why a particular exchange was rejected to our
 * response data.
 *
 * @param[in,out] oc order context to update
 * @param exchange_url exchange this is about
 * @param ec error code to set for the exchange
 */
static void
add_rejection (struct OrderContext *oc,
               const char *exchange_url,
               enum TALER_ErrorCode ec)
{
  if (NULL == oc->set_exchanges.exchange_rejections)
  {
    oc->set_exchanges.exchange_rejections = json_array ();
    GNUNET_assert (NULL != oc->set_exchanges.exchange_rejections);
  }
  GNUNET_assert (0 ==
                 json_array_append_new (
                   oc->set_exchanges.exchange_rejections,
                   GNUNET_JSON_PACK (
                     GNUNET_JSON_pack_string ("exchange_url",
                                              exchange_url),
                     TALER_JSON_pack_ec (ec))));
}


/**
 * 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 */
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Exchange %s with currency `%s' is not applicable to this order\n",
                exchange_url,
                TMH_EXCHANGES_get_currency (exchange));
    add_rejection (oc,
                   exchange_url,
                   TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH);
    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);
    add_rejection (oc,
                   exchange_url,
                   TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LEGALLY_REFUSED);
    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));
  GNUNET_assert (0 <=
                 TALER_amount_set_add (&wmc->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;
    add_rejection (oc,
                   rx->url,
                   TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE);
    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);

    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Exchange `%s' has wire method `%s'\n",
                rx->url,
                wire_method);
    for (struct WireMethodCandidate *wmc = oc->add_payment_details.wmc_head;
         NULL != wmc;
         wmc = wmc->next)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                  "Order could use wire method `%s'\n",
                  wmc->wm->wire_method);
      if (0 == strcmp (wmc->wm->wire_method,
                       wire_method) )
      {
        applicable |= get_acceptable (oc,
                                      exchange,
                                      rx->url,
                                      wmc);
      }
    }
    GNUNET_free (wire_method);
  }
  if ( (! applicable) &&
       (! oc->set_exchanges.forced_reload) )
  {
    /* Checks for 'forced_reload' to not log the error *again*
       if we forced a re-load and are encountering the
       applicability error a 2nd time */
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Exchange `%s' %u wire methods are not applicable to this order\n",
                rx->url,
                keys->accounts_len);
    add_rejection (oc,
                   rx->url,
                   TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED)
    ;
  }
  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 **************** */


/**
 * Helper function to sort uint64_t array with qsort().
 *
 * @param a pointer to element to compare
 * @param b pointer to element to compare
 * @return 0 on equal, -1 on smaller, 1 on larger
 */
static int
uint64_cmp (const void *a,
            const void *b)
{
  uint64_t ua = *(const uint64_t *) a;
  uint64_t ub = *(const uint64_t *) b;

  if (ua < ub)
    return -1;
  if (ua > ub)
    return 1;
  return 0;
}


/**
 * 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)
{
  uint64_t pots[oc->parse_order.products_len + 1];
  size_t pots_off = 0;

  if (0 != oc->parse_order.order_default_money_pot)
    pots[pots_off++] = oc->parse_order.order_default_money_pot;
  /**
   * parse_request.inventory_products => instructions to add products to contract terms
   * parse_order.products => contains products that are not from the backend-managed inventory.
   */
  oc->merge_inventory.products = json_array ();
  for (size_t i = 0; i<oc->parse_order.products_len; i++)
  {
    GNUNET_assert (
      0 ==
      json_array_append_new (
        oc->merge_inventory.products,
        TALER_MERCHANT_product_sold_serialize (&oc->parse_order.products[i])));
    if (0 != oc->parse_order.products[i].product_money_pot)
      pots[pots_off++] = oc->parse_order.products[i].product_money_pot;
  }

  /* make sure pots array only has distinct elements */
  qsort (pots,
         pots_off,
         sizeof (uint64_t),
         &uint64_cmp);
  {
    size_t e = 0;

    for (size_t i = 1; i<pots_off; i++)
    {
      if (pots[e] != pots[i])
        pots[++e] = pots[i];
    }
    if (pots_off > 0)
      e++;
    pots_off = e;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Found %u unique money pots in order\n",
              (unsigned int) pots_off);

  /* check if all money pots exist; note that we do NOT treat
     the inventory products to this check, as (1) the foreign key
     constraint should ensure this, and (2) if the money pot
     were deleted (concurrently), the value is specified to be
     considered 0 (aka none) and so we can proceed anyway. */
  if (pots_off > 0)
  {
    enum GNUNET_DB_QueryStatus qs;
    uint64_t pot_missing;

    qs = TMH_db->check_money_pots (TMH_db->cls,
                                   oc->hc->instance->settings.id,
                                   pots_off,
                                   pots,
                                   &pot_missing);
    switch (qs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
    case GNUNET_DB_STATUS_SOFT_ERROR:
      GNUNET_break (0);
      reply_with_error (oc,
                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                        TALER_EC_GENERIC_DB_FETCH_FAILED,
                        "check_money_pots");
      return;
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      /* great, good case! */
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "All money pots exist\n");
      break;
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      {
        char mstr[32];

        GNUNET_snprintf (mstr,
                         sizeof (mstr),
                         "%llu",
                         (unsigned long long) pot_missing);
        reply_with_error (oc,
                          MHD_HTTP_NOT_FOUND,
                          TALER_EC_MERCHANT_GENERIC_MONEY_POT_UNKNOWN,
                          mstr);
        return;
      }
    }
  }

  /* 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 !=
            TALER_MERCHANT_vk_process_quantity_inputs (
              TALER_MERCHANT_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;
        }
      }
      {
        struct TALER_MERCHANT_ProductSold ps = {
          .product_id = (char *) ip->product_id,
          .product_name = pd.product_name,
          .description = pd.description,
          .description_i18n = pd.description_i18n,
          .unit_quantity.integer = ip->quantity,
          .unit_quantity.fractional = ip->quantity_frac,
          .prices_length = pd.price_array_length,
          .prices = GNUNET_new_array (pd.price_array_length,
                                      struct TALER_Amount),
          .prices_are_net = pd.price_is_net,
          .image = pd.image,
          .taxes = pd.taxes,
          .delivery_date = oc->parse_order.delivery_date,
          .product_money_pot = pd.money_pot_id,
          .unit = pd.unit,

        };
        json_t *p;
        char unit_quantity_buf[64];

        for (size_t j = 0; j<pd.price_array_length; j++)
        {
          struct TALER_Amount atomic_amount;

          TALER_amount_set_zero (pd.price_array[j].currency,
                                 &atomic_amount);
          atomic_amount.fraction = 1;
          GNUNET_assert (
            GNUNET_OK ==
            TALER_MERCHANT_amount_multiply_by_quantity (
              &ps.prices[j],
              &pd.price_array[j],
              &ps.unit_quantity,
              TALER_MERCHANT_ROUND_UP,
              &atomic_amount));
        }

        TALER_MERCHANT_vk_format_fractional_string (
          TALER_MERCHANT_VK_QUANTITY,
          ip->quantity,
          ip->quantity_frac,
          sizeof (unit_quantity_buf),
          unit_quantity_buf);
        if (0 != pd.money_pot_id)
          pots[pots_off++] = pd.money_pot_id;
        p = TALER_MERCHANT_product_sold_serialize (&ps);
        GNUNET_assert (NULL != p);
        GNUNET_free (ps.prices);
        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 ("tip",
                                    &choice->tip),
        &choice->no_tip),
      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 ( (! choice->no_tip) &&
         (GNUNET_OK !=
          TALER_amount_cmp_currency (&choice->amount,
                                     &choice->tip)) )
    {
      GNUNET_break_op (0);
      GNUNET_JSON_parse_free (spec);
      reply_with_error (oc,
                        MHD_HTTP_BAD_REQUEST,
                        TALER_EC_GENERIC_CURRENCY_MISMATCH,
                        "tip and amount");
      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 json_t *products = 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",
                                    &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_mark_optional (
      GNUNET_JSON_spec_uint64 ("order_default_money_pot",
                               &oc->parse_order.order_default_money_pot),
      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 != products) &&
       (0 != (oc->parse_order.products_len = json_array_size (products))) )
  {
    size_t i;
    json_t *p;

    oc->parse_order.products
      = GNUNET_new_array (oc->parse_order.products_len,
                          struct TALER_MERCHANT_ProductSold);
    json_array_foreach (products, i, p)
    {
      if (GNUNET_OK !=
          TALER_MERCHANT_parse_product_sold (p,
                                             &oc->parse_order.products[i]))
      {
        GNUNET_break_op (0);
        reply_with_error (oc,
                          MHD_HTTP_BAD_REQUEST,
                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
                          "order.products");
        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 (
            "tip",
            &oc->parse_order.details.v0.tip),
          &oc->parse_order.details.v0.no_tip),
        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 ( (! oc->parse_order.details.v0.no_tip) &&
           (GNUNET_OK !=
            TALER_amount_cmp_currency (&oc->parse_order.details.v0.brutto,
                                       &oc->parse_order.details.v0.tip)) )
      {
        GNUNET_break_op (0);
        GNUNET_JSON_parse_free (spec);
        reply_with_error (oc,
                          MHD_HTTP_BAD_REQUEST,
                          TALER_EC_GENERIC_CURRENCY_MISMATCH,
                          "tip and amount");
        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;
  }

  /* 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_op (0);
      GNUNET_JSON_parse_free (spec);
      reply_with_error (oc,
                        MHD_HTTP_BAD_REQUEST,
                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
                        "inventory_products (too many)");
      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_mark_optional (
          GNUNET_JSON_spec_uint64 ("product_money_pot",
                                   &ipr->product_money_pot),
          NULL),
        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_post-private-orders.c */
