/*
   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-orders-ID-pay.c
 * @brief handling of POST /orders/$ID/pay requests
 * @author Marcello Stanisci
 * @author Christian Grothoff
 * @author Florian Dold
 */
#include "platform.h"
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_db_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_error_codes.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_exchange_service.h>
#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_post-orders-ID-pay.h"
#include "taler-merchant-httpd_private-get-orders.h"
#include "taler_merchant_util.h"
#include "taler_merchantdb_plugin.h"

#ifdef HAVE_DONAU_DONAU_SERVICE_H
#include <donau/donau_service.h>
#include <donau/donau_util.h>
#include <donau/donau_json_lib.h>
#endif

/**
 * How often do we retry the (complex!) database transaction?
 */
#define MAX_RETRIES 5

/**
 * Maximum number of coins that we allow per transaction.
 * Note that the limit for each batch deposit request to
 * the exchange is lower, so we may break a very large
 * number of coins up into multiple smaller requests to
 * the exchange.
 */
#define MAX_COIN_ALLOWED_COINS 1024

/**
 * Maximum number of tokens that we allow as inputs per transaction
 */
#define MAX_TOKEN_ALLOWED_INPUTs 64

/**
 * Maximum number of tokens that we allow as outputs per transaction
 */
#define MAX_TOKEN_ALLOWED_OUTPUTs 64

/**
 * How often do we ask the exchange again about our
 * KYC status? Very rarely, as if the user actively
 * changes it, we should usually notice anyway.
 */
#define KYC_RETRY_FREQUENCY GNUNET_TIME_UNIT_WEEKS

/**
 * Information we keep for an individual call to the pay handler.
 */
struct PayContext;


/**
 * Different phases of processing the /pay request.
 */
enum PayPhase
{
  /**
   * Initial phase where the request is parsed.
   */
  PP_PARSE_PAY = 0,

  /**
   * Parse wallet data object from the pay request.
   */
  PP_PARSE_WALLET_DATA,

  /**
   * Check database state for the given order.
   */
  PP_CHECK_CONTRACT,

  /**
   * Validate provided tokens and token envelopes.
   */
  PP_VALIDATE_TOKENS,

  /**
   * Check if contract has been paid.
   */
  PP_CONTRACT_PAID,

  /**
   * Execute payment transaction.
   */
  PP_PAY_TRANSACTION,

  /**
   * Communicate with DONAU to generate a donation receipt from the donor BUDIs.
   */
  PP_REQUEST_DONATION_RECEIPT,

  /**
   * Process the donation receipt response from DONAU (save the donau_sigs to the db).
   */
  PP_FINAL_OUTPUT_TOKEN_PROCESSING,

  /**
   * Notify other processes about successful payment.
   */
  PP_PAYMENT_NOTIFICATION,

  /**
   * Create final success response.
   */
  PP_SUCCESS_RESPONSE,

  /**
   * Perform batch deposits with exchange(s).
   */
  PP_BATCH_DEPOSITS,

  /**
   * Return response in payment context.
   */
  PP_RETURN_RESPONSE,

  /**
   * An exchange denied a deposit, fail for
   * legal reasons.
   */
  PP_FAIL_LEGAL_REASONS,

  /**
   * Return #MHD_YES to end processing.
   */
  PP_END_YES,

  /**
   * Return #MHD_NO to end processing.
   */
  PP_END_NO
};


/**
 * Information kept during a pay request for each coin.
 */
struct DepositConfirmation
{

  /**
   * Reference to the main PayContext
   */
  struct PayContext *pc;

  /**
   * URL of the exchange that issued this coin.
   */
  char *exchange_url;

  /**
   * Details about the coin being deposited.
   */
  struct TALER_EXCHANGE_CoinDepositDetail cdd;

  /**
   * Fee charged by the exchange for the deposit operation of this coin.
   */
  struct TALER_Amount deposit_fee;

  /**
   * Fee charged by the exchange for the refund operation of this coin.
   */
  struct TALER_Amount refund_fee;

  /**
   * If a minimum age was required (i. e. pc->minimum_age is large enough),
   * this is the signature of the minimum age (as a single uint8_t), using the
   * private key to the corresponding age group.  Might be all zeroes for no
   * age attestation.
   */
  struct TALER_AgeAttestationP minimum_age_sig;

  /**
   * If a minimum age was required (i. e. pc->minimum_age is large enough),
   * this is the age commitment (i. e. age mask and vector of EdDSA public
   * keys, one per age group) that went into the mining of the coin.  The
   * SHA256 hash of the mask and the vector of public keys was bound to the
   * key.
   */
  struct TALER_AgeCommitment age_commitment;

  /**
   * Age mask in the denomination that defines the age groups.  Only
   * applicable, if minimum age was required.
   */
  struct TALER_AgeMask age_mask;

  /**
   * Offset of this coin into the `dc` array of all coins in the
   * @e pc.
   */
  unsigned int index;

  /**
   * true, if no field "age_commitment" was found in the JSON blob
   */
  bool no_age_commitment;

  /**
   * True, if no field "minimum_age_sig" was found in the JSON blob
   */
  bool no_minimum_age_sig;

  /**
   * true, if no field "h_age_commitment" was found in the JSON blob
   */
  bool no_h_age_commitment;

  /**
   * true if we found this coin in the database.
   */
  bool found_in_db;

  /**
   * true if we #deposit_paid_check() matched this coin in the database.
   */
  bool matched_in_db;

};

struct TokenUseConfirmation
{

  /**
   * Signature on the deposit request made using the token use private key.
   */
  struct TALER_TokenUseSignatureP sig;

  /**
   * Token use public key. This key was blindly signed by the merchant during
   * the token issuance process.
   */
  struct TALER_TokenUsePublicKeyP pub;

  /**
   * Unblinded signature on the token use public key done by the merchant.
   */
  struct TALER_TokenIssueSignature unblinded_sig;

  /**
   * Hash of the token issue public key associated with this token.
   * Note this is set in the validate_tokens phase.
   */
  struct TALER_TokenIssuePublicKeyHashP h_issue;

  /**
   * true if we found this token in the database.
   */
  bool found_in_db;

};


/**
 * Information about a token envelope.
 */
struct TokenEnvelope
{

  /**
   * Blinded token use public keys waiting to be signed.
   */
  struct TALER_TokenEnvelope blinded_token;

};


/**
 * (Blindly) signed token to be returned to the wallet.
 */
struct SignedOutputToken
{

  /**
   * Index of the output token that produced
   * this blindly signed token.
   */
  unsigned int output_index;

  /**
   * Blinded token use public keys waiting to be signed.
   */
  struct TALER_BlindedTokenIssueSignature sig;

  /**
   * Hash of token issue public key.
   */
  struct TALER_TokenIssuePublicKeyHashP h_issue;

};


/**
 * Information kept during a pay request for each exchange.
 */
struct ExchangeGroup
{

  /**
   * Payment context this group is part of.
   */
  struct PayContext *pc;

  /**
   * Handle to the batch deposit operation we are performing for this
   * exchange, NULL after the operation is done.
   */
  struct TALER_EXCHANGE_BatchDepositHandle *bdh;

  /**
   * Handle for operation to lookup /keys (and auditors) from
   * the exchange used for this transaction; NULL if no operation is
   * pending.
   */
  struct TMH_EXCHANGES_KeysOperation *fo;

  /**
   * URL of the exchange that issued this coin. Aliases
   * the exchange URL of one of the coins, do not free!
   */
  const char *exchange_url;

  /**
   * Total deposit amount in this exchange group.
   */
  struct TALER_Amount total;

  /**
   * Wire fee that applies to this exchange for the
   * given payment context's wire method.
   */
  struct TALER_Amount wire_fee;

  /**
   * true if we already tried a forced /keys download.
   */
  bool tried_force_keys;

  /**
   * Did this exchange deny the transaction for legal reasons?
   */
  bool got_451;
};


/**
 * Information about donau, that can be fetched even
 * if the merhchant doesn't support donau
 */
struct DonauData
{
  /**
   * The user-selected Donau URL.
   */
  char *donau_url;

  /**
   * The donation year, as parsed from "year".
   */
  uint64_t donation_year;

  /**
   * The original BUDI key-pairs array from the donor
   * to be used for the receipt creation.
   */
  const json_t *budikeypairs;
};

/**
 * Information we keep for an individual call to the /pay handler.
 */
struct PayContext
{

  /**
   * Stored in a DLL.
   */
  struct PayContext *next;

  /**
   * Stored in a DLL.
   */
  struct PayContext *prev;

  /**
   * MHD connection to return to
   */
  struct MHD_Connection *connection;

  /**
   * Details about the client's request.
   */
  struct TMH_HandlerContext *hc;

  /**
   * Transaction ID given in @e root.
   */
  const char *order_id;

  /**
   * Response to return, NULL if we don't have one yet.
   */
  struct MHD_Response *response;

  /**
   * Array with @e output_tokens_len signed tokens returned in
   * the response to the wallet.
   */
  struct SignedOutputToken *output_tokens;

  /**
   * Number of output tokens to return in the response.
   * Length of the @e output_tokens array.
   */
  unsigned int output_tokens_len;

  /**
   * Counter used to generate the output index in append_output_token_sig().
   */
  unsigned int output_index_gen;

  /**
   * Counter used to generate the output index in append_output_token_sig().
   *
   * Counts the generated tokens _within_ the current output_index_gen.
   */
  unsigned int output_token_cnt;

  /**
   * HTTP status code to use for the reply, i.e 200 for "OK".
   * Special value UINT_MAX is used to indicate hard errors
   * (no reply, return #MHD_NO).
   */
  unsigned int response_code;

  /**
   * Payment processing phase we are in.
   */
  enum PayPhase phase;

  /**
   * #GNUNET_NO if the @e connection was not suspended,
   * #GNUNET_YES if the @e connection was suspended,
   * #GNUNET_SYSERR if @e connection was resumed to as
   * part of #MH_force_pc_resume during shutdown.
   */
  enum GNUNET_GenericReturnValue suspended;

  /**
   * Results from the phase_parse_pay()
   */
  struct
  {

    /**
     * Array with @e num_exchanges exchanges we are depositing
     * coins into.
     */
    struct ExchangeGroup **egs;

    /**
     * Array with @e coins_cnt coins we are despositing.
     */
    struct DepositConfirmation *dc;

    /**
     * Array with @e tokens_cnt input tokens passed to this request.
     */
    struct TokenUseConfirmation *tokens;

    /**
     * Optional session id given in @e root.
     * NULL if not given.
     */
    char *session_id;

    /**
     * Wallet data json object from the request. Containing additional
     * wallet data such as the selected choice_index.
     */
    const json_t *wallet_data;

    /**
     * Number of coins this payment is made of.  Length
     * of the @e dc array.
     */
    size_t coins_cnt;

    /**
     * Number of input tokens passed to this request.  Length
     * of the @e tokens array.
     */
    size_t tokens_cnt;

    /**
     * Number of exchanges involved in the payment. Length
     * of the @e eg array.
     */
    unsigned int num_exchanges;

  } parse_pay;

  /**
   * Results from the phase_wallet_data()
   */
  struct
  {

    /**
     * Array with @e token_envelopes_cnt (blinded) token envelopes.
     */
    struct TokenEnvelope *token_envelopes;

    /**
     * Index of selected choice in the @e contract_terms choices array.
     */
    int16_t choice_index;

    /**
     * Number of token envelopes passed to this request.
     * Length of the @e token_envelopes array.
     */
    size_t token_envelopes_cnt;

    /**
     * Hash of the canonicalized wallet data json object.
     */
    struct GNUNET_HashCode h_wallet_data;

    /**
     * Donau related information
     */
    struct DonauData donau;

    /**
     * Serial from the DB of the donau instance that we are using
     */
    uint64_t donau_instance_serial;

#ifdef  HAVE_DONAU_DONAU_SERVICE_H
    /**
     * Number of the blinded key pairs @e bkps
     */
    unsigned int num_bkps;

    /**
     * Blinded key pairs received from the wallet
     */
    struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps;

    /**
     * The id of the charity as saved on the donau.
     */
    uint64_t charity_id;

    /**
     * Private key of the charity(related to the private key of the merchant).
     */
    struct DONAU_CharityPrivateKeyP charity_priv;

    /**
     * Maximum amount of donations that the charity can receive per year.
     */
    struct TALER_Amount charity_max_per_year;

    /**
     * Amount of donations that the charity has received so far this year.
     */
    struct TALER_Amount charity_receipts_to_date;

    /**
     * Donau keys, that we are using to get the information about the bkps.
     */
    struct DONAU_Keys *donau_keys;

    /**
     * Amount from BKPS
     */
    struct TALER_Amount donation_amount;
#endif

  } parse_wallet_data;

  /**
   * Results from the phase_check_contract()
   */
  struct
  {

    /**
     * Hashed @e contract_terms.
     */
    struct TALER_PrivateContractHashP h_contract_terms;

    /**
     * Our contract (or NULL if not available).
     */
    json_t *contract_terms_json;

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

    /**
     * What wire method (of the @e mi) was selected by the wallet?
     * Set in #phase_parse_pay().
     */
    struct TMH_WireMethod *wm;

    /**
     * Set to the POS key, if applicable for this order.
     */
    char *pos_key;

    /**
     * Serial number of this order in the database (set once we did the lookup).
     */
    uint64_t order_serial;

    /**
     * Algorithm chosen for generating the confirmation code.
     */
    enum TALER_MerchantConfirmationAlgorithm pos_alg;

  } check_contract;

  /**
   * Results from the phase_validate_tokens()
   */
  struct
  {

    /**
     * Maximum fee the merchant is willing to pay, from @e root.
     * Note that IF the total fee of the exchange is higher, that is
     * acceptable to the merchant if the customer is willing to
     * pay the difference
     * (i.e. amount - max_fee <= actual_amount - actual_fee).
     */
    struct TALER_Amount max_fee;

    /**
     * Amount from @e root.  This is the amount the merchant expects
     * to make, minus @e max_fee.
     */
    struct TALER_Amount brutto;

    /**
     * Index of the donau output in the list of tokens.
     * Set to -1 if no donau output exists.
     */
    int donau_output_index;

  } validate_tokens;

  /**
   * Results from the phase_execute_pay_transaction()
   */
  struct
  {

    /**
     * Considering all the coins with the "found_in_db" flag
     * set, what is the total amount we were so far paid on
     * this contract?
     */
    struct TALER_Amount total_paid;

    /**
     * Considering all the coins with the "found_in_db" flag
     * set, what is the total amount we had to pay in deposit
     * fees so far on this contract?
     */
    struct TALER_Amount total_fees_paid;

    /**
     * Considering all the coins with the "found_in_db" flag
     * set, what is the total amount we already refunded?
     */
    struct TALER_Amount total_refunded;

    /**
     * Number of coin deposits pending.
     */
    unsigned int pending;

    /**
     * How often have we retried the 'main' transaction?
     */
    unsigned int retry_counter;

    /**
     * Set to true if the deposit currency of a coin
     * does not match the contract currency.
     */
    bool deposit_currency_mismatch;

    /**
     * Set to true if the database contains a (bogus)
     * refund for a different currency.
     */
    bool refund_currency_mismatch;

  } pay_transaction;

  /**
   * Results from the phase_batch_deposits()
   */
  struct
  {

    /**
     * Task called when the (suspended) processing for
     * the /pay request times out.
     * Happens when we don't get a response from the exchange.
     */
    struct GNUNET_SCHEDULER_Task *timeout_task;

    /**
     * Number of batch transactions pending.
     */
    unsigned int pending_at_eg;

    /**
     * Did any exchange deny a deposit for legal reasons?
     */
    bool got_451;

  } batch_deposits;

#ifdef HAVE_DONAU_DONAU_SERVICE_H
  /**
   * Struct for #phase_request_donation_receipt()
   */
  struct
  {
    /**
     * Handler of the donau request
     */
    struct DONAU_BatchIssueReceiptHandle *birh;

  } donau_receipt;
#endif
};


/**
 * Head of active pay context DLL.
 */
static struct PayContext *pc_head;

/**
 * Tail of active pay context DLL.
 */
static struct PayContext *pc_tail;


void
TMH_force_pc_resume ()
{
  for (struct PayContext *pc = pc_head;
       NULL != pc;
       pc = pc->next)
  {
    if (NULL != pc->batch_deposits.timeout_task)
    {
      GNUNET_SCHEDULER_cancel (pc->batch_deposits.timeout_task);
      pc->batch_deposits.timeout_task = NULL;
    }
    if (GNUNET_YES == pc->suspended)
    {
      pc->suspended = GNUNET_SYSERR;
      MHD_resume_connection (pc->connection);
    }
  }
}


/**
 * Resume payment processing.
 *
 * @param[in,out] pc payment process to resume
 */
static void
pay_resume (struct PayContext *pc)
{
  GNUNET_assert (GNUNET_YES == pc->suspended);
  pc->suspended = GNUNET_NO;
  MHD_resume_connection (pc->connection);
  TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
}


/**
 * Resume the given pay context and send the given response.
 * Stores the response in the @a pc and signals MHD to resume
 * the connection.  Also ensures MHD runs immediately.
 *
 * @param pc payment context
 * @param response_code response code to use
 * @param response response data to send back
 */
static void
resume_pay_with_response (struct PayContext *pc,
                          unsigned int response_code,
                          struct MHD_Response *response)
{
  pc->response_code = response_code;
  pc->response = response;
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Resuming /pay handling. HTTP status for our reply is %u.\n",
              response_code);
  for (unsigned int i = 0; i<pc->parse_pay.num_exchanges; i++)
  {
    struct ExchangeGroup *eg = pc->parse_pay.egs[i];

    if (NULL != eg->fo)
    {
      TMH_EXCHANGES_keys4exchange_cancel (eg->fo);
      eg->fo = NULL;
      pc->batch_deposits.pending_at_eg--;
    }
    if (NULL != eg->bdh)
    {
      TALER_EXCHANGE_batch_deposit_cancel (eg->bdh);
      eg->bdh = NULL;
      pc->batch_deposits.pending_at_eg--;
    }
  }
  GNUNET_assert (0 == pc->batch_deposits.pending_at_eg);
  if (NULL != pc->batch_deposits.timeout_task)
  {
    GNUNET_SCHEDULER_cancel (pc->batch_deposits.timeout_task);
    pc->batch_deposits.timeout_task = NULL;
  }
  pc->phase = PP_RETURN_RESPONSE;
  pay_resume (pc);
}


/**
 * Resume payment processing with an error.
 *
 * @param pc operation to resume
 * @param ec taler error code to return
 * @param msg human readable error message
 */
static void
resume_pay_with_error (struct PayContext *pc,
                       enum TALER_ErrorCode ec,
                       const char *msg)
{
  resume_pay_with_response (
    pc,
    TALER_ErrorCode_get_http_status_safe (ec),
    TALER_MHD_make_error (ec,
                          msg));
}


/**
 * Conclude payment processing for @a pc with the
 * given @a res MHD status code.
 *
 * @param[in,out] pc payment context for final state transition
 * @param res MHD return code to end with
 */
static void
pay_end (struct PayContext *pc,
         MHD_RESULT res)
{
  pc->phase = (MHD_YES == res)
    ? PP_END_YES
    : PP_END_NO;
}


/**
 * Return response stored in @a pc.
 *
 * @param[in,out] pc payment context we are processing
 */
static void
phase_return_response (struct PayContext *pc)
{
  GNUNET_assert (0 != pc->response_code);
  /* We are *done* processing the request, just queue the response (!) */
  if (UINT_MAX == pc->response_code)
  {
    GNUNET_break (0);
    pay_end (pc,
             MHD_NO); /* hard error */
    return;
  }
  pay_end (pc,
           MHD_queue_response (pc->connection,
                               pc->response_code,
                               pc->response));
}


/**
 * Return a response indicating failure for legal reasons.
 *
 * @param[in,out] pc payment context we are processing
 */
static void
phase_fail_for_legal_reasons (struct PayContext *pc)
{
  json_t *exchanges;

  GNUNET_assert (0 == pc->pay_transaction.pending);
  GNUNET_assert (pc->batch_deposits.got_451);
  exchanges = json_array ();
  GNUNET_assert (NULL != exchanges);
  for (unsigned int i = 0; i<pc->parse_pay.num_exchanges; i++)
  {
    struct ExchangeGroup *eg = pc->parse_pay.egs[i];

    GNUNET_assert (NULL == eg->fo);
    GNUNET_assert (NULL == eg->bdh);
    if (! eg->got_451)
      continue;
    GNUNET_assert (
      0 ==
      json_array_append_new (
        exchanges,
        json_string (eg->exchange_url)));
  }
  pay_end (pc,
           TALER_MHD_REPLY_JSON_PACK (
             pc->connection,
             MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
             TALER_JSON_pack_ec (
               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_LEGALLY_REFUSED),
             GNUNET_JSON_pack_array_steal ("exchange_base_urls",
                                           exchanges)));
}


/**
 * Do database transaction for a completed batch deposit.
 *
 * @param eg group that completed
 * @param dr response from the server
 * @return transaction status
 */
static enum GNUNET_DB_QueryStatus
batch_deposit_transaction (const struct ExchangeGroup *eg,
                           const struct TALER_EXCHANGE_BatchDepositResult *dr)
{
  const struct PayContext *pc = eg->pc;
  enum GNUNET_DB_QueryStatus qs;
  struct TALER_Amount total_without_fees;
  uint64_t b_dep_serial;
  uint32_t off = 0;

  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (pc->validate_tokens.brutto.currency,
                                        &total_without_fees));
  for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
  {
    struct DepositConfirmation *dc = &pc->parse_pay.dc[i];
    struct TALER_Amount amount_without_fees;

    /* might want to group deposits by batch more explicitly ... */
    if (0 != strcmp (eg->exchange_url,
                     dc->exchange_url))
      continue;
    if (dc->found_in_db)
      continue;
    GNUNET_assert (0 <=
                   TALER_amount_subtract (&amount_without_fees,
                                          &dc->cdd.amount,
                                          &dc->deposit_fee));
    GNUNET_assert (0 <=
                   TALER_amount_add (&total_without_fees,
                                     &total_without_fees,
                                     &amount_without_fees));
  }
  qs = TMH_db->insert_deposit_confirmation (
    TMH_db->cls,
    pc->hc->instance->settings.id,
    dr->details.ok.deposit_timestamp,
    &pc->check_contract.h_contract_terms,
    eg->exchange_url,
    pc->check_contract.contract_terms->wire_deadline,
    &total_without_fees,
    &eg->wire_fee,
    &pc->check_contract.wm->h_wire,
    dr->details.ok.exchange_sig,
    dr->details.ok.exchange_pub,
    &b_dep_serial);
  if (qs <= 0)
    return qs; /* Entire batch already known or failure, we're done */

  for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
  {
    struct DepositConfirmation *dc = &pc->parse_pay.dc[i];

    /* might want to group deposits by batch more explicitly ... */
    if (0 != strcmp (eg->exchange_url,
                     dc->exchange_url))
      continue;
    if (dc->found_in_db)
      continue;
    /* FIXME-#9457: We might want to check if the order was fully paid concurrently
       by some other wallet here, and if so, issue an auto-refund. Right now,
       it is possible to over-pay if two wallets literally make a concurrent
       payment, as the earlier check for 'paid' is not in the same transaction
       scope as this 'insert' operation. */
    qs = TMH_db->insert_deposit (
      TMH_db->cls,
      off++, /* might want to group deposits by batch more explicitly ... */
      b_dep_serial,
      &dc->cdd.coin_pub,
      &dc->cdd.coin_sig,
      &dc->cdd.amount,
      &dc->deposit_fee,
      &dc->refund_fee,
      GNUNET_TIME_absolute_add (
        pc->check_contract.contract_terms->wire_deadline.abs_time,
        GNUNET_TIME_randomize (GNUNET_TIME_UNIT_MINUTES)));
    if (qs < 0)
      return qs;
    GNUNET_break (qs > 0);
  }
  return qs;
}


/**
 * Handle case where the batch deposit completed
 * with a status of #MHD_HTTP_OK.
 *
 * @param eg group that completed
 * @param dr response from the server
 */
static void
handle_batch_deposit_ok (struct ExchangeGroup *eg,
                         const struct TALER_EXCHANGE_BatchDepositResult *dr)
{
  struct PayContext *pc = eg->pc;
  enum GNUNET_DB_QueryStatus qs
    = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;

  /* store result to DB */
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Storing successful payment %s (%s) at instance `%s'\n",
              pc->hc->infix,
              GNUNET_h2s (&pc->check_contract.h_contract_terms.hash),
              pc->hc->instance->settings.id);
  for (unsigned int r = 0; r<MAX_RETRIES; r++)
  {
    TMH_db->preflight (TMH_db->cls);
    if (GNUNET_OK !=
        TMH_db->start (TMH_db->cls,
                       "batch-deposit-insert-confirmation"))
    {
      resume_pay_with_response (
        pc,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        TALER_MHD_MAKE_JSON_PACK (
          TALER_JSON_pack_ec (
            TALER_EC_GENERIC_DB_START_FAILED),
          TMH_pack_exchange_reply (&dr->hr)));
      return;
    }
    qs = batch_deposit_transaction (eg,
                                    dr);
    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    {
      TMH_db->rollback (TMH_db->cls);
      continue;
    }
    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    {
      GNUNET_break (0);
      resume_pay_with_error (pc,
                             TALER_EC_GENERIC_DB_COMMIT_FAILED,
                             "batch_deposit_transaction");
    }
    qs = TMH_db->commit (TMH_db->cls);
    if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    {
      TMH_db->rollback (TMH_db->cls);
      continue;
    }
    if (GNUNET_DB_STATUS_HARD_ERROR == qs)
    {
      GNUNET_break (0);
      resume_pay_with_error (pc,
                             TALER_EC_GENERIC_DB_COMMIT_FAILED,
                             "insert_deposit");
    }
    break; /* DB transaction succeeded */
  }
  if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
  {
    resume_pay_with_error (pc,
                           TALER_EC_GENERIC_DB_SOFT_FAILURE,
                           "insert_deposit");
    return;
  }

  /* Transaction is done, mark affected coins as complete as well. */
  for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
  {
    struct DepositConfirmation *dc = &pc->parse_pay.dc[i];

    if (0 != strcmp (eg->exchange_url,
                     pc->parse_pay.dc[i].exchange_url))
      continue;
    if (dc->found_in_db)
      continue;
    dc->found_in_db = true;     /* well, at least NOW it'd be true ;-) */
    pc->pay_transaction.pending--;
  }
}


/**
 * Notify taler-merchant-kyccheck that we got a KYC
 * rule violation notification and should start to
 * check our KYC status.
 *
 * @param eg exchange group we were notified for
 */
static void
notify_kyc_required (const struct ExchangeGroup *eg)
{
  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 (
    &eg->pc->check_contract.contract_terms->h_wire,
    sizeof (eg->pc->check_contract.contract_terms->h_wire));
  GNUNET_asprintf (&extra,
                   "%s %s",
                   hws,
                   eg->exchange_url);
  GNUNET_free (hws);
  TMH_db->event_notify (TMH_db->cls,
                        &es,
                        extra,
                        strlen (extra) + 1);
  GNUNET_free (extra);
}


/**
 * Callback to handle a batch deposit permission's response.
 *
 * @param cls a `struct ExchangeGroup`
 * @param dr HTTP response code details
 */
static void
batch_deposit_cb (
  void *cls,
  const struct TALER_EXCHANGE_BatchDepositResult *dr)
{
  struct ExchangeGroup *eg = cls;
  struct PayContext *pc = eg->pc;

  eg->bdh = NULL;
  pc->batch_deposits.pending_at_eg--;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Batch deposit completed with status %u\n",
              dr->hr.http_status);
  GNUNET_assert (GNUNET_YES == pc->suspended);
  switch (dr->hr.http_status)
  {
  case MHD_HTTP_OK:
    handle_batch_deposit_ok (eg,
                             dr);
    if (0 == pc->batch_deposits.pending_at_eg)
    {
      pc->phase = PP_PAY_TRANSACTION;
      pay_resume (pc);
    }
    return;
  case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
    notify_kyc_required (eg);
    eg->got_451 = true;
    pc->batch_deposits.got_451 = true;
    /* update pc->pay_transaction.pending */
    for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
    {
      struct DepositConfirmation *dc = &pc->parse_pay.dc[i];

      if (0 != strcmp (eg->exchange_url,
                       pc->parse_pay.dc[i].exchange_url))
        continue;
      if (dc->found_in_db)
        continue;
      pc->pay_transaction.pending--;
    }
    if (0 == pc->batch_deposits.pending_at_eg)
    {
      pc->phase = PP_PAY_TRANSACTION;
      pay_resume (pc);
    }
    return;
  default:
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Deposit operation failed with HTTP code %u/%d\n",
                dr->hr.http_status,
                (int) dr->hr.ec);
    /* Transaction failed */
    if (5 == dr->hr.http_status / 100)
    {
      /* internal server error at exchange */
      resume_pay_with_response (pc,
                                MHD_HTTP_BAD_GATEWAY,
                                TALER_MHD_MAKE_JSON_PACK (
                                  TALER_JSON_pack_ec (
                                    TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS),
                                  TMH_pack_exchange_reply (&dr->hr)));
      return;
    }
    if (NULL == dr->hr.reply)
    {
      /* We can't do anything meaningful here, the exchange did something wrong */
      resume_pay_with_response (
        pc,
        MHD_HTTP_BAD_GATEWAY,
        TALER_MHD_MAKE_JSON_PACK (
          TALER_JSON_pack_ec (
            TALER_EC_MERCHANT_GENERIC_EXCHANGE_REPLY_MALFORMED),
          TMH_pack_exchange_reply (&dr->hr)));
      return;
    }

    /* Forward error, adding the "exchange_url" for which the
       error was being generated */
    if (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS == dr->hr.ec)
    {
      resume_pay_with_response (
        pc,
        MHD_HTTP_CONFLICT,
        TALER_MHD_MAKE_JSON_PACK (
          TALER_JSON_pack_ec (
            TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS),
          TMH_pack_exchange_reply (&dr->hr),
          GNUNET_JSON_pack_string ("exchange_url",
                                   eg->exchange_url)));
      return;
    }
    resume_pay_with_response (
      pc,
      MHD_HTTP_BAD_GATEWAY,
      TALER_MHD_MAKE_JSON_PACK (
        TALER_JSON_pack_ec (
          TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNEXPECTED_STATUS),
        TMH_pack_exchange_reply (&dr->hr),
        GNUNET_JSON_pack_string ("exchange_url",
                                 eg->exchange_url)));
    return;
  } /* end switch */
}


/**
 * Force re-downloading keys for @a eg.
 *
 * @param[in,out] eg group to re-download keys for
 */
static void
force_keys (struct ExchangeGroup *eg);


/**
 * Function called with the result of our exchange keys lookup.
 *
 * @param cls the `struct ExchangeGroup`
 * @param keys the keys of the exchange
 * @param exchange representation of the exchange
 */
static void
process_pay_with_keys (
  void *cls,
  struct TALER_EXCHANGE_Keys *keys,
  struct TMH_Exchange *exchange)
{
  struct ExchangeGroup *eg = cls;
  struct PayContext *pc = eg->pc;
  struct TMH_HandlerContext *hc = pc->hc;
  unsigned int group_size;
  struct TALER_Amount max_amount;
  enum TMH_ExchangeStatus es;

  eg->fo = NULL;
  pc->batch_deposits.pending_at_eg--;
  GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Processing payment with keys from exchange %s\n",
              eg->exchange_url);
  GNUNET_assert (GNUNET_YES == pc->suspended);
  if (NULL == keys)
  {
    GNUNET_break_op (0);
    resume_pay_with_error (
      pc,
      TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
      NULL);
    return;
  }
  if (! TMH_EXCHANGES_is_below_limit (keys,
                                      TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION,
                                      &eg->total))
  {
    GNUNET_break_op (0);
    resume_pay_with_error (
      pc,
      TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_TRANSACTION_LIMIT_VIOLATION,
      eg->exchange_url);
    return;
  }

  max_amount = eg->total;
  es = TMH_exchange_check_debit (
    pc->hc->instance->settings.id,
    exchange,
    pc->check_contract.wm,
    &max_amount);
  if ( (TMH_ES_OK != es) &&
       (TMH_ES_RETRY_OK != es) )
  {
    if (eg->tried_force_keys ||
        (0 == (TMH_ES_RETRY_OK & es)) )
    {
      GNUNET_break_op (0);
      resume_pay_with_error (
        pc,
        TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED,
        NULL);
      return;
    }
    force_keys (eg);
    return;
  }
  if (-1 ==
      TALER_amount_cmp (&max_amount,
                        &eg->total))
  {
    /* max_amount < eg->total */
    GNUNET_break_op (0);
    resume_pay_with_error (
      pc,
      TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_TRANSACTION_LIMIT_VIOLATION,
      eg->exchange_url);
    return;
  }

  if (GNUNET_OK !=
      TMH_EXCHANGES_lookup_wire_fee (exchange,
                                     pc->check_contract.wm->wire_method,
                                     &eg->wire_fee))
  {
    if (eg->tried_force_keys)
    {
      GNUNET_break_op (0);
      resume_pay_with_error (
        pc,
        TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_METHOD_UNSUPPORTED,
        pc->check_contract.wm->wire_method);
      return;
    }
    force_keys (eg);
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Got wire data for %s\n",
              eg->exchange_url);

  /* Initiate /batch-deposit operation for all coins of
     the current exchange (!) */
  group_size = 0;
  for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
  {
    struct DepositConfirmation *dc = &pc->parse_pay.dc[i];
    const struct TALER_EXCHANGE_DenomPublicKey *denom_details;
    bool is_age_restricted_denom = false;

    if (0 != strcmp (eg->exchange_url,
                     pc->parse_pay.dc[i].exchange_url))
      continue;
    if (dc->found_in_db)
      continue;

    denom_details
      = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
                                                     &dc->cdd.h_denom_pub);
    if (NULL == denom_details)
    {
      if (eg->tried_force_keys)
      {
        GNUNET_break_op (0);
        resume_pay_with_response (
          pc,
          MHD_HTTP_BAD_REQUEST,
          TALER_MHD_MAKE_JSON_PACK (
            TALER_JSON_pack_ec (
              TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_KEY_NOT_FOUND),
            GNUNET_JSON_pack_data_auto ("h_denom_pub",
                                        &dc->cdd.h_denom_pub),
            GNUNET_JSON_pack_allow_null (
              GNUNET_JSON_pack_object_steal (
                "exchange_keys",
                TALER_EXCHANGE_keys_to_json (keys)))));
        return;
      }
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Missing denomination %s from exchange %s, updating keys\n",
                  GNUNET_h2s (&dc->cdd.h_denom_pub.hash),
                  eg->exchange_url);
      force_keys (eg);
      return;
    }
    dc->deposit_fee = denom_details->fees.deposit;
    dc->refund_fee = denom_details->fees.refund;

    if (GNUNET_TIME_absolute_is_past (
          denom_details->expire_deposit.abs_time))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Denomination key offered by client has expired for deposits\n");
      resume_pay_with_response (
        pc,
        MHD_HTTP_GONE,
        TALER_MHD_MAKE_JSON_PACK (
          TALER_JSON_pack_ec (
            TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DENOMINATION_DEPOSIT_EXPIRED),
          GNUNET_JSON_pack_data_auto ("h_denom_pub",
                                      &denom_details->h_key)));
      return;
    }

    /* Now that we have the details about the denomination, we can verify age
     * restriction requirements, if applicable. Note that denominations with an
     * age_mask equal to zero always pass the age verification.  */
    is_age_restricted_denom = (0 != denom_details->key.age_mask.bits);

    if (is_age_restricted_denom &&
        (0 < pc->check_contract.contract_terms->minimum_age))
    {
      /* Minimum age given and restricted coin provided: We need to verify the
       * minimum age */
      unsigned int code = 0;

      if (dc->no_age_commitment)
      {
        GNUNET_break_op (0);
        code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_MISSING;
        goto AGE_FAIL;
      }
      dc->age_commitment.mask = denom_details->key.age_mask;
      if (((int) (dc->age_commitment.num + 1)) !=
          __builtin_popcount (dc->age_commitment.mask.bits))
      {
        GNUNET_break_op (0);
        code =
          TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_SIZE_MISMATCH;
        goto AGE_FAIL;
      }
      if (GNUNET_OK !=
          TALER_age_commitment_verify (
            &dc->age_commitment,
            pc->check_contract.contract_terms->minimum_age,
            &dc->minimum_age_sig))
        code = TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_VERIFICATION_FAILED;
AGE_FAIL:
      if (0 < code)
      {
        GNUNET_break_op (0);
        TALER_age_commitment_free (&dc->age_commitment);
        resume_pay_with_response (
          pc,
          MHD_HTTP_BAD_REQUEST,
          TALER_MHD_MAKE_JSON_PACK (
            TALER_JSON_pack_ec (code),
            GNUNET_JSON_pack_data_auto ("h_denom_pub",
                                        &denom_details->h_key)));
        return;
      }

      /* Age restriction successfully verified!
       * Calculate the hash of the age commitment. */
      TALER_age_commitment_hash (&dc->age_commitment,
                                 &dc->cdd.h_age_commitment);
      TALER_age_commitment_free (&dc->age_commitment);
    }
    else if (is_age_restricted_denom &&
             dc->no_h_age_commitment)
    {
      /* The contract did not ask for a minimum_age but the client paid
       * with a coin that has age restriction enabled.  We lack the hash
       * of the age commitment in this case in order to verify the coin
       * and to deposit it with the exchange. */
      GNUNET_break_op (0);
      resume_pay_with_response (
        pc,
        MHD_HTTP_BAD_REQUEST,
        TALER_MHD_MAKE_JSON_PACK (
          TALER_JSON_pack_ec (
            TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AGE_COMMITMENT_HASH_MISSING),
          GNUNET_JSON_pack_data_auto ("h_denom_pub",
                                      &denom_details->h_key)));
      return;
    }
    group_size++;
  }

  if (0 == group_size)
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Group size zero, %u batch transactions remain pending\n",
                pc->batch_deposits.pending_at_eg);
    if (0 == pc->batch_deposits.pending_at_eg)
    {
      pc->phase = PP_PAY_TRANSACTION;
      pay_resume (pc);
      return;
    }
    return;
  }
  if (group_size > TALER_MAX_COINS)
    group_size = TALER_MAX_COINS;
  {
    struct TALER_EXCHANGE_CoinDepositDetail cdds[group_size];
    struct TALER_EXCHANGE_DepositContractDetail dcd = {
      .wire_deadline = pc->check_contract.contract_terms->wire_deadline,
      .merchant_payto_uri = pc->check_contract.wm->payto_uri,
      .wire_salt = pc->check_contract.wm->wire_salt,
      .h_contract_terms = pc->check_contract.h_contract_terms,
      .wallet_data_hash = pc->parse_wallet_data.h_wallet_data,
      .wallet_timestamp = pc->check_contract.contract_terms->timestamp,
      .merchant_pub = hc->instance->merchant_pub,
      .refund_deadline = pc->check_contract.contract_terms->refund_deadline
    };
    enum TALER_ErrorCode ec;
    size_t off = 0;

    TALER_merchant_contract_sign (&pc->check_contract.h_contract_terms,
                                  &pc->hc->instance->merchant_priv,
                                  &dcd.merchant_sig);
    for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
    {
      struct DepositConfirmation *dc = &pc->parse_pay.dc[i];

      if (dc->found_in_db)
        continue;
      if (0 != strcmp (dc->exchange_url,
                       eg->exchange_url))
        continue;
      cdds[off++] = dc->cdd;
      if (off >= group_size)
        break;
    }
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Initiating batch deposit with %u coins\n",
                group_size);
    /* Note: the coin signatures over the wallet_data_hash are
       checked inside of this call */
    eg->bdh = TALER_EXCHANGE_batch_deposit (
      TMH_curl_ctx,
      eg->exchange_url,
      keys,
      &dcd,
      group_size,
      cdds,
      &batch_deposit_cb,
      eg,
      &ec);
    if (NULL == eg->bdh)
    {
      /* Signature was invalid or some other constraint was not satisfied.  If
         the exchange was unavailable, we'd get that information in the
         callback. */
      GNUNET_break_op (0);
      resume_pay_with_response (
        pc,
        TALER_ErrorCode_get_http_status_safe (ec),
        TALER_MHD_MAKE_JSON_PACK (
          TALER_JSON_pack_ec (ec),
          GNUNET_JSON_pack_string ("exchange_url",
                                   eg->exchange_url)));
      return;
    }
    pc->batch_deposits.pending_at_eg++;
    if (TMH_force_audit)
      TALER_EXCHANGE_batch_deposit_force_dc (eg->bdh);
  }
}


static void
force_keys (struct ExchangeGroup *eg)
{
  struct PayContext *pc = eg->pc;

  eg->tried_force_keys = true;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Forcing /keys download (once)\n");
  eg->fo = TMH_EXCHANGES_keys4exchange (
    eg->exchange_url,
    true,
    &process_pay_with_keys,
    eg);
  if (NULL == eg->fo)
  {
    GNUNET_break_op (0);
    resume_pay_with_error (pc,
                           TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED,
                           eg->exchange_url);
    return;
  }
  pc->batch_deposits.pending_at_eg++;
}


/**
 * Handle a timeout for the processing of the pay request.
 *
 * @param cls our `struct PayContext`
 */
static void
handle_pay_timeout (void *cls)
{
  struct PayContext *pc = cls;

  pc->batch_deposits.timeout_task = NULL;
  GNUNET_assert (GNUNET_YES == pc->suspended);
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Resuming pay with error after timeout\n");
  resume_pay_with_error (pc,
                         TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT,
                         NULL);
}


/**
 * Compute the timeout for a /pay request based on the number of coins
 * involved.
 *
 * @param num_coins number of coins
 * @returns timeout for the /pay request
 */
static struct GNUNET_TIME_Relative
get_pay_timeout (unsigned int num_coins)
{
  struct GNUNET_TIME_Relative t;

  /* FIXME-Performance-Optimization: Do some benchmarking to come up with a
   * better timeout.  We've increased this value so the wallet integration
   * test passes again on my (Florian) machine.
   */
  t = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
                                     15 * (1 + (num_coins / 5)));

  return t;
}


/**
 * Start batch deposits for all exchanges involved
 * in this payment.
 *
 * @param[in,out] pc payment context we are processing
 */
static void
phase_batch_deposits (struct PayContext *pc)
{
  for (unsigned int i = 0; i<pc->parse_pay.num_exchanges; i++)
  {
    struct ExchangeGroup *eg = pc->parse_pay.egs[i];
    bool have_coins = false;

    for (size_t j = 0; j<pc->parse_pay.coins_cnt; j++)
    {
      struct DepositConfirmation *dc = &pc->parse_pay.dc[j];

      if (0 != strcmp (eg->exchange_url,
                       dc->exchange_url))
        continue;
      if (dc->found_in_db)
        continue;
      have_coins = true;
      break;
    }
    if (! have_coins)
      continue; /* no coins left to deposit at this exchange */
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Getting /keys for %s\n",
                eg->exchange_url);
    eg->fo = TMH_EXCHANGES_keys4exchange (
      eg->exchange_url,
      false,
      &process_pay_with_keys,
      eg);
    if (NULL == eg->fo)
    {
      GNUNET_break_op (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_BAD_REQUEST,
                 TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED,
                 eg->exchange_url));
      return;
    }
    pc->batch_deposits.pending_at_eg++;
  }
  if (0 == pc->batch_deposits.pending_at_eg)
  {
    pc->phase = PP_PAY_TRANSACTION;
    pay_resume (pc);
    return;
  }
  /* Suspend while we interact with the exchange */
  MHD_suspend_connection (pc->connection);
  pc->suspended = GNUNET_YES;
  GNUNET_assert (NULL == pc->batch_deposits.timeout_task);
  pc->batch_deposits.timeout_task
    = GNUNET_SCHEDULER_add_delayed (get_pay_timeout (pc->parse_pay.coins_cnt),
                                    &handle_pay_timeout,
                                    pc);
}


/**
 * Build JSON array of blindly signed token envelopes,
 * to be used in the response to the wallet.
 *
 * @param[in,out] pc payment context to use
 */
static json_t *
build_token_sigs (struct PayContext *pc)
{
  json_t *token_sigs;

  if (0 == pc->output_tokens_len)
    return NULL;
  token_sigs = json_array ();
  GNUNET_assert (NULL != token_sigs);
  for (unsigned int i = 0; i < pc->output_tokens_len; i++)
  {
    GNUNET_assert (0 ==
                   json_array_append_new (
                     token_sigs,
                     GNUNET_JSON_PACK (
                       GNUNET_JSON_pack_blinded_sig (
                         "blind_sig",
                         pc->output_tokens[i].sig.signature)
                       )));
  }
  return token_sigs;
}


/**
 * Generate response (payment successful)
 *
 * @param[in,out] pc payment context where the payment was successful
 */
static void
phase_success_response (struct PayContext *pc)
{
  struct TALER_MerchantSignatureP sig;
  char *pos_confirmation;

  /* Sign on our end (as the payment did go through, even if it may
     have been refunded already) */
  TALER_merchant_pay_sign (&pc->check_contract.h_contract_terms,
                           &pc->hc->instance->merchant_priv,
                           &sig);
  /* Build the response */
  pos_confirmation = (NULL == pc->check_contract.pos_key)
    ? NULL
    : TALER_build_pos_confirmation (pc->check_contract.pos_key,
                                    pc->check_contract.pos_alg,
                                    &pc->validate_tokens.brutto,
                                    pc->check_contract.contract_terms->timestamp
                                    );
  pay_end (pc,
           TALER_MHD_REPLY_JSON_PACK (
             pc->connection,
             MHD_HTTP_OK,
             GNUNET_JSON_pack_allow_null (
               GNUNET_JSON_pack_string ("pos_confirmation",
                                        pos_confirmation)),
             GNUNET_JSON_pack_allow_null (
               GNUNET_JSON_pack_array_steal ("token_sigs",
                                             build_token_sigs (pc))),
             GNUNET_JSON_pack_data_auto ("sig",
                                         &sig)));
  GNUNET_free (pos_confirmation);
}


/**
 * Use database to notify other clients about the
 * payment being completed.
 *
 * @param[in,out] pc context to trigger notification for
 */
static void
phase_payment_notification (struct PayContext *pc)
{
  {
    struct TMH_OrderPayEventP pay_eh = {
      .header.size = htons (sizeof (pay_eh)),
      .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID),
      .merchant_pub = pc->hc->instance->merchant_pub
    };

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Notifying clients about payment of order %s\n",
                pc->order_id);
    GNUNET_CRYPTO_hash (pc->order_id,
                        strlen (pc->order_id),
                        &pay_eh.h_order_id);
    TMH_db->event_notify (TMH_db->cls,
                          &pay_eh.header,
                          NULL,
                          0);
  }
  if ( (NULL != pc->parse_pay.session_id) &&
       (NULL != pc->check_contract.contract_terms->fulfillment_url) )
  {
    struct TMH_SessionEventP session_eh = {
      .header.size = htons (sizeof (session_eh)),
      .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
      .merchant_pub = pc->hc->instance->merchant_pub
    };

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Notifying clients about session change to %s for %s\n",
                pc->parse_pay.session_id,
                pc->check_contract.contract_terms->fulfillment_url);
    GNUNET_CRYPTO_hash (pc->parse_pay.session_id,
                        strlen (pc->parse_pay.session_id),
                        &session_eh.h_session_id);
    GNUNET_CRYPTO_hash (pc->check_contract.contract_terms->fulfillment_url,
                        strlen (pc->check_contract.contract_terms->
                                fulfillment_url),
                        &session_eh.h_fulfillment_url);
    TMH_db->event_notify (TMH_db->cls,
                          &session_eh.header,
                          NULL,
                          0);
  }
  pc->phase = PP_SUCCESS_RESPONSE;
}


/**
 * Phase to write all outputs to our database so we do
 * not re-request them in case the client re-plays the
 * request.
 *
 * @param[in,out] pc payment context
 */
static void
phase_final_output_token_processing (struct PayContext *pc)
{
  if (0 == pc->output_tokens_len)
  {
    pc->phase++;
    return;
  }
  for (unsigned int retry = 0; retry < MAX_RETRIES; retry++)
  {
    enum GNUNET_DB_QueryStatus qs;

    TMH_db->preflight (TMH_db->cls);
    if (GNUNET_OK !=
        TMH_db->start (TMH_db->cls,
                       "insert_order_blinded_sigs"))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "start insert_order_blinded_sigs_failed");
      pc->phase++;
      return;
    }
#ifdef HAVE_DONAU_DONAU_SERVICE_H
    if (pc->parse_wallet_data.num_bkps > 0)
    {
      qs = TMH_db->update_donau_instance_receipts_amount (
        TMH_db->cls,
        &pc->parse_wallet_data.donau_instance_serial,
        &pc->parse_wallet_data.charity_receipts_to_date);
      switch (qs)
      {
      case GNUNET_DB_STATUS_HARD_ERROR:
        TMH_db->rollback (TMH_db->cls);
        GNUNET_break (0);
        return;
      case GNUNET_DB_STATUS_SOFT_ERROR:
        TMH_db->rollback (TMH_db->cls);
        continue;
      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
        /* weird for an update */
        GNUNET_break (0);
        break;
      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
        break;
      }
    }
#endif
    for (unsigned int i = 0;
         i < pc->output_tokens_len;
         i++)
    {
      qs = TMH_db->insert_order_blinded_sigs (
        TMH_db->cls,
        pc->order_id,
        i,
        &pc->output_tokens[i].h_issue.hash,
        pc->output_tokens[i].sig.signature);

      switch (qs)
      {
      case GNUNET_DB_STATUS_HARD_ERROR:
        TMH_db->rollback (TMH_db->cls);
        pc->phase++;
        return;
      case GNUNET_DB_STATUS_SOFT_ERROR:
        TMH_db->rollback (TMH_db->cls);
        goto OUTER;
      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
        /* weird for an update */
        GNUNET_break (0);
        break;
      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
        break;
      }
    } /* for i */
    qs = TMH_db->commit (TMH_db->cls);
    switch (qs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
      TMH_db->rollback (TMH_db->cls);
      pc->phase++;
      return;
    case GNUNET_DB_STATUS_SOFT_ERROR:
      TMH_db->rollback (TMH_db->cls);
      continue;
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      pc->phase++;
      return; /* success */
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      pc->phase++;
      return; /* success */
    }
    GNUNET_break (0);
    pc->phase++;
    return; /* strange */
OUTER:
  } /* for retry */
  TMH_db->rollback (TMH_db->cls);
  pc->phase++;
  /* We continue anyway, as there is not much we can
     do here: the Donau *did* issue us the receipts;
     also, we'll eventually ask the Donau for the
     balance and get the correct one. Plus, we were
     paid by the client, so it's technically all still
     OK. If the request fails anyway, the wallet will
     most likely replay the request and then hopefully
     we will succeed the next time */
}


#ifdef HAVE_DONAU_DONAU_SERVICE_H

/**
 * Add donation receipt outputs to the output_tokens.
 *
 * Note that under the current (odd, bad) libdonau
 * API *we* are responsible for freeing blinded_sigs,
 * so we truly own that array!
 *
 * @param[in,out] pc payment context
 * @param num_blinded_sigs number of signatures received
 * @param blinded_sigs blinded signatures from Donau
 * @return #GNUNET_OK on success,
 *         #GNUNET_SYSERR on failure (state machine was
 *          in that case already advanced)
 */
static enum GNUNET_GenericReturnValue
add_donation_receipt_outputs (
  struct PayContext *pc,
  size_t num_blinded_sigs,
  struct DONAU_BlindedDonationUnitSignature *blinded_sigs)
{
  int donau_output_index = pc->validate_tokens.donau_output_index;

  GNUNET_assert (pc->parse_wallet_data.num_bkps ==
                 num_blinded_sigs);

  GNUNET_assert (donau_output_index >= 0);

  for (unsigned int i = 0; i<pc->output_tokens_len; i++)
  {
    struct SignedOutputToken *sot
      = &pc->output_tokens[i];

    /* Only look at actual donau tokens. */
    if (sot->output_index != donau_output_index)
      continue;

    sot->sig.signature = GNUNET_CRYPTO_blind_sig_incref (blinded_sigs[i].
                                                         blinded_sig);
    sot->h_issue.hash = pc->parse_wallet_data.bkps[i].h_donation_unit_pub.hash;
  }
  return GNUNET_OK;
}


/**
 * Callback to handle the result of a batch issue request.
 *
 * @param cls our `struct PayContext`
 * @param resp the response from Donau
 */
static void
merchant_donau_issue_receipt_cb (
  void *cls,
  const struct DONAU_BatchIssueResponse *resp)
{
  struct PayContext *pc = cls;
  /* Donau replies asynchronously, so we expect the PayContext
   * to be suspended. */
  GNUNET_assert (GNUNET_YES == pc->suspended);
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Donau responded with status=%u, ec=%u",
              resp->hr.http_status,
              resp->hr.ec);
  switch (resp->hr.http_status)
  {
  case 0:
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Donau batch issue request from merchant-httpd failed (http_status==0)");
    resume_pay_with_error (pc,
                           TALER_EC_MERCHANT_GENERIC_DONAU_INVALID_RESPONSE,
                           resp->hr.hint);
    return;
  case MHD_HTTP_OK:
  case MHD_HTTP_CREATED:
    if (TALER_EC_NONE != resp->hr.ec)
    {
      /* Most probably, it is just some small flaw from
       * donau so no point in failing, yet we have to display it */
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Donau signalled error %u despite HTTP %u",
                  resp->hr.ec,
                  resp->hr.http_status);
    }
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Donau accepted donation receipts with total_issued=%s",
                TALER_amount2s (&resp->details.ok.issued_amount));
    if (GNUNET_OK !=
        add_donation_receipt_outputs (pc,
                                      resp->details.ok.num_blinded_sigs,
                                      resp->details.ok.blinded_sigs))
      return; /* state machine was already advanced */
    pc->phase = PP_FINAL_OUTPUT_TOKEN_PROCESSING;
    pay_resume (pc);
    return;

  case MHD_HTTP_BAD_REQUEST:
  case MHD_HTTP_FORBIDDEN:
  case MHD_HTTP_NOT_FOUND:
  case MHD_HTTP_INTERNAL_SERVER_ERROR:
  default: /* make sure that everything except 200/201 will end up here*/
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Donau replied with HTTP %u (ec=%u)",
                resp->hr.http_status,
                resp->hr.ec);
    resume_pay_with_error (pc,
                           TALER_EC_MERCHANT_GENERIC_DONAU_INVALID_RESPONSE,
                           resp->hr.hint);
    return;
  }
}


/**
 * Parse a bkp encoded in JSON.
 *
 * @param[out] bkp where to return the result
 * @param bkp_key_obj json to parse
 * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if @a bkp_key_obj
 * is malformed.
 */
static enum GNUNET_GenericReturnValue
merchant_parse_json_bkp (struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp,
                         const json_t *bkp_key_obj)
{
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_fixed_auto ("h_donation_unit_pub",
                                 &bkp->h_donation_unit_pub),
    DONAU_JSON_spec_blinded_donation_identifier ("blinded_udi",
                                                 &bkp->blinded_udi),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (bkp_key_obj,
                         spec,
                         NULL,
                         NULL))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Generate a donation signature for the bkp and charity.
 *
 * @param[in,out] pc payment context containing the charity and bkps
 */
static void
phase_request_donation_receipt (struct PayContext *pc)
{
  if ( (NULL == pc->parse_wallet_data.donau.donau_url) ||
       (0 == pc->parse_wallet_data.num_bkps) )
  {
    pc->phase++;
    return;
  }
  pc->donau_receipt.birh =
    DONAU_charity_issue_receipt (
      TMH_curl_ctx,
      pc->parse_wallet_data.donau.donau_url,
      &pc->parse_wallet_data.charity_priv,
      pc->parse_wallet_data.charity_id,
      pc->parse_wallet_data.donau.donation_year,
      pc->parse_wallet_data.num_bkps,
      pc->parse_wallet_data.bkps,
      &merchant_donau_issue_receipt_cb,
      pc);
  if (NULL == pc->donau_receipt.birh)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to create Donau receipt request");
    pay_end (pc,
             TALER_MHD_reply_with_error (pc->connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
                                         "Donau request creation error"));
    return;
  }
  MHD_suspend_connection (pc->connection);
  pc->suspended = GNUNET_YES;
}


#endif


/**
 * Function called with information about a coin that was deposited.
 *
 * @param cls closure
 * @param exchange_url exchange where @a coin_pub was deposited
 * @param coin_pub public key of the coin
 * @param amount_with_fee amount the exchange will deposit for this coin
 * @param deposit_fee fee the exchange will charge for this coin
 * @param refund_fee fee the exchange will charge for refunding this coin
 */
static void
check_coin_paid (void *cls,
                 const char *exchange_url,
                 const struct TALER_CoinSpendPublicKeyP *coin_pub,
                 const struct TALER_Amount *amount_with_fee,
                 const struct TALER_Amount *deposit_fee,
                 const struct TALER_Amount *refund_fee)
{
  struct PayContext *pc = cls;

  for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
  {
    struct DepositConfirmation *dc = &pc->parse_pay.dc[i];

    if (dc->found_in_db)
      continue; /* processed earlier, skip "expensive" memcmp() */
    /* Get matching coin from results*/
    if ( (0 != GNUNET_memcmp (coin_pub,
                              &dc->cdd.coin_pub)) ||
         (0 !=
          strcmp (exchange_url,
                  dc->exchange_url)) ||
         (GNUNET_OK !=
          TALER_amount_cmp_currency (amount_with_fee,
                                     &dc->cdd.amount)) ||
         (0 != TALER_amount_cmp (amount_with_fee,
                                 &dc->cdd.amount)) )
      continue; /* does not match, skip */
    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Deposit of coin `%s' already in our DB.\n",
                TALER_B2S (coin_pub));
    if ( (GNUNET_OK !=
          TALER_amount_cmp_currency (&pc->pay_transaction.total_paid,
                                     amount_with_fee)) ||
         (GNUNET_OK !=
          TALER_amount_cmp_currency (&pc->pay_transaction.total_fees_paid,
                                     deposit_fee)) )
    {
      GNUNET_break_op (0);
      pc->pay_transaction.deposit_currency_mismatch = true;
      break;
    }
    GNUNET_assert (0 <=
                   TALER_amount_add (&pc->pay_transaction.total_paid,
                                     &pc->pay_transaction.total_paid,
                                     amount_with_fee));
    GNUNET_assert (0 <=
                   TALER_amount_add (&pc->pay_transaction.total_fees_paid,
                                     &pc->pay_transaction.total_fees_paid,
                                     deposit_fee));
    dc->deposit_fee = *deposit_fee;
    dc->refund_fee = *refund_fee;
    dc->cdd.amount = *amount_with_fee;
    dc->found_in_db = true;
    pc->pay_transaction.pending--;
  }
}


/**
 * Function called with information about a refund.  Check if this coin was
 * claimed by the wallet for the transaction, and if so add the refunded
 * amount to the pc's "total_refunded" amount.
 *
 * @param cls closure with a `struct PayContext`
 * @param coin_pub public coin from which the refund comes from
 * @param refund_amount refund amount which is being taken from @a coin_pub
 */
static void
check_coin_refunded (void *cls,
                     const struct TALER_CoinSpendPublicKeyP *coin_pub,
                     const struct TALER_Amount *refund_amount)
{
  struct PayContext *pc = cls;

  /* We look at refunds here that apply to the coins
     that the customer is currently trying to pay us with.

     Such refunds are not "normal" refunds, but abort-pay refunds, which are
     given in the case that the wallet aborts the payment.
     In the case the wallet then decides to complete the payment *after* doing
     an abort-pay refund (an unusual but possible case), we need
     to make sure that existing refunds are accounted for. */

  for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
  {
    struct DepositConfirmation *dc = &pc->parse_pay.dc[i];

    /* Get matching coins from results.  */
    if (0 != GNUNET_memcmp (coin_pub,
                            &dc->cdd.coin_pub))
      continue;
    if (GNUNET_OK !=
        TALER_amount_cmp_currency (&pc->pay_transaction.total_refunded,
                                   refund_amount))
    {
      GNUNET_break (0);
      pc->pay_transaction.refund_currency_mismatch = true;
      break;
    }
    GNUNET_assert (0 <=
                   TALER_amount_add (&pc->pay_transaction.total_refunded,
                                     &pc->pay_transaction.total_refunded,
                                     refund_amount));
    break;
  }
}


/**
 * Check whether the amount paid is sufficient to cover the price.
 *
 * @param pc payment context to check
 * @return true if the payment is sufficient, false if it is
 *         insufficient
 */
static bool
check_payment_sufficient (struct PayContext *pc)
{
  struct TALER_Amount acc_fee;
  struct TALER_Amount acc_amount;
  struct TALER_Amount final_amount;
  struct TALER_Amount total_wire_fee;
  struct TALER_Amount total_needed;

  if (0 == pc->parse_pay.coins_cnt)
    return TALER_amount_is_zero (&pc->validate_tokens.brutto);
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (pc->validate_tokens.brutto.currency,
                                        &total_wire_fee));
  for (unsigned int i = 0; i < pc->parse_pay.num_exchanges; i++)
  {
    if (GNUNET_OK !=
        TALER_amount_cmp_currency (&total_wire_fee,
                                   &pc->parse_pay.egs[i]->wire_fee))
    {
      GNUNET_break_op (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_BAD_REQUEST,
                                           TALER_EC_GENERIC_CURRENCY_MISMATCH,
                                           total_wire_fee.currency));
      return false;
    }
    if (0 >
        TALER_amount_add (&total_wire_fee,
                          &total_wire_fee,
                          &pc->parse_pay.egs[i]->wire_fee))
    {
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_INTERNAL_SERVER_ERROR,
                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_WIRE_FEE_ADDITION_FAILED,
                 "could not add exchange wire fee to total"));
      return false;
    }
  }

  /**
   * This loops calculates what are the deposit fee / total
   * amount with fee / and wire fee, for all the coins.
   */
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (pc->validate_tokens.brutto.currency,
                                        &acc_fee));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (pc->validate_tokens.brutto.currency,
                                        &acc_amount));
  for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
  {
    struct DepositConfirmation *dc = &pc->parse_pay.dc[i];

    GNUNET_assert (dc->found_in_db);
    if ( (GNUNET_OK !=
          TALER_amount_cmp_currency (&acc_fee,
                                     &dc->deposit_fee)) ||
         (GNUNET_OK !=
          TALER_amount_cmp_currency (&acc_amount,
                                     &dc->cdd.amount)) )
    {
      GNUNET_break_op (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_BAD_REQUEST,
                 TALER_EC_GENERIC_CURRENCY_MISMATCH,
                 dc->deposit_fee.currency));
      return false;
    }
    if ( (0 >
          TALER_amount_add (&acc_fee,
                            &dc->deposit_fee,
                            &acc_fee)) ||
         (0 >
          TALER_amount_add (&acc_amount,
                            &dc->cdd.amount,
                            &acc_amount)) )
    {
      GNUNET_break (0);
      /* Overflow in these amounts? Very strange. */
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_INTERNAL_SERVER_ERROR,
                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
                 "Overflow adding up amounts"));
      return false;
    }
    if (1 ==
        TALER_amount_cmp (&dc->deposit_fee,
                          &dc->cdd.amount))
    {
      GNUNET_break_op (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_BAD_REQUEST,
                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_FEES_EXCEED_PAYMENT,
                 "Deposit fees exceed coin's contribution"));
      return false;
    }
  } /* end deposit loop */

  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Amount received from wallet: %s\n",
              TALER_amount2s (&acc_amount));
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Deposit fee for all coins: %s\n",
              TALER_amount2s (&acc_fee));
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Total wire fee: %s\n",
              TALER_amount2s (&total_wire_fee));
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Deposit fee limit for merchant: %s\n",
              TALER_amount2s (&pc->validate_tokens.max_fee));
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "Total refunded amount: %s\n",
              TALER_amount2s (&pc->pay_transaction.total_refunded));

  /* Now compare exchange wire fee compared to what we are willing to pay */
  if (GNUNET_YES !=
      TALER_amount_cmp_currency (&total_wire_fee,
                                 &acc_fee))
  {
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_BAD_REQUEST,
               TALER_EC_GENERIC_CURRENCY_MISMATCH,
               total_wire_fee.currency));
    return false;
  }

  /* add wire fee to the total fees */
  if (0 >
      TALER_amount_add (&acc_fee,
                        &acc_fee,
                        &total_wire_fee))
  {
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_INTERNAL_SERVER_ERROR,
               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
               "Overflow adding up amounts"));
    return false;
  }
  if (-1 == TALER_amount_cmp (&pc->validate_tokens.max_fee,
                              &acc_fee))
  {
    /**
     * Sum of fees of *all* the different exchanges of all the coins are
     * higher than the fixed limit that the merchant is willing to pay.  The
     * difference must be paid by the customer.
     */
    struct TALER_Amount excess_fee;

    /* compute fee amount to be covered by customer */
    GNUNET_assert (TALER_AAR_RESULT_POSITIVE ==
                   TALER_amount_subtract (&excess_fee,
                                          &acc_fee,
                                          &pc->validate_tokens.max_fee));
    /* add that to the total */
    if (0 >
        TALER_amount_add (&total_needed,
                          &excess_fee,
                          &pc->validate_tokens.brutto))
    {
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_INTERNAL_SERVER_ERROR,
                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
                 "Overflow adding up amounts"));
      return false;
    }
  }
  else
  {
    /* Fees are fully covered by the merchant, all we require
       is that the total payment is not below the contract's amount */
    total_needed = pc->validate_tokens.brutto;
  }

  /* Do not count refunds towards the payment */
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Subtracting total refunds from paid amount: %s\n",
              TALER_amount2s (&pc->pay_transaction.total_refunded));
  if (0 >
      TALER_amount_subtract (&final_amount,
                             &acc_amount,
                             &pc->pay_transaction.total_refunded))
  {
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_INTERNAL_SERVER_ERROR,
               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDS_EXCEED_PAYMENTS,
               "refunded amount exceeds total payments"));
    return false;
  }

  if (-1 == TALER_amount_cmp (&final_amount,
                              &total_needed))
  {
    /* acc_amount < total_needed */
    if (-1 < TALER_amount_cmp (&acc_amount,
                               &total_needed))
    {
      GNUNET_break_op (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_PAYMENT_REQUIRED,
                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUNDED,
                 "contract not paid up due to refunds"));
      return false;
    }
    if (-1 < TALER_amount_cmp (&acc_amount,
                               &pc->validate_tokens.brutto))
    {
      GNUNET_break_op (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_BAD_REQUEST,
                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_DUE_TO_FEES,
                 "contract not paid up due to fees (client may have calculated them badly)"));
      return false;
    }
    GNUNET_break_op (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_BAD_REQUEST,
               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_PAYMENT_INSUFFICIENT,
               "payment insufficient"));
    return false;
  }
  return true;
}


/**
 * Execute the DB transaction.  If required (from
 * soft/serialization errors), the transaction can be
 * restarted here.
 *
 * @param[in,out] pc payment context to transact
 */
static void
phase_execute_pay_transaction (struct PayContext *pc)
{
  struct TMH_HandlerContext *hc = pc->hc;
  const char *instance_id = hc->instance->settings.id;

  if (pc->batch_deposits.got_451)
  {
    pc->phase = PP_FAIL_LEGAL_REASONS;
    return;
  }
  /* Avoid re-trying transactions on soft errors forever! */
  if (pc->pay_transaction.retry_counter++ > MAX_RETRIES)
  {
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (pc->connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_SOFT_FAILURE,
                                         NULL));
    return;
  }

  /* Initialize some amount accumulators
     (used in check_coin_paid(), check_coin_refunded()
     and check_payment_sufficient()). */
  GNUNET_break (GNUNET_OK ==
                TALER_amount_set_zero (pc->validate_tokens.brutto.currency,
                                       &pc->pay_transaction.total_paid));
  GNUNET_break (GNUNET_OK ==
                TALER_amount_set_zero (pc->validate_tokens.brutto.currency,
                                       &pc->pay_transaction.total_fees_paid));
  GNUNET_break (GNUNET_OK ==
                TALER_amount_set_zero (pc->validate_tokens.brutto.currency,
                                       &pc->pay_transaction.total_refunded));
  for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
    pc->parse_pay.dc[i].found_in_db = false;
  pc->pay_transaction.pending = pc->parse_pay.coins_cnt;

  /* First, try to see if we have all we need already done */
  TMH_db->preflight (TMH_db->cls);
  if (GNUNET_OK !=
      TMH_db->start (TMH_db->cls,
                     "run pay"))
  {
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (pc->connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_START_FAILED,
                                         NULL));
    return;
  }

  for (size_t i = 0; i<pc->parse_pay.tokens_cnt; i++)
  {
    struct TokenUseConfirmation *tuc = &pc->parse_pay.tokens[i];
    enum GNUNET_DB_QueryStatus qs;

    /* Insert used token into database, the unique constraint will
       case an error if this token was used before. */
    qs = TMH_db->insert_spent_token (TMH_db->cls,
                                     &pc->check_contract.h_contract_terms,
                                     &tuc->h_issue,
                                     &tuc->pub,
                                     &tuc->sig,
                                     &tuc->unblinded_sig);

    switch (qs)
    {
    case GNUNET_DB_STATUS_SOFT_ERROR:
      TMH_db->rollback (TMH_db->cls);
      return; /* do it again */
    case GNUNET_DB_STATUS_HARD_ERROR:
      /* Always report on hard error as well to enable diagnostics */
      TMH_db->rollback (TMH_db->cls);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_GENERIC_DB_STORE_FAILED,
                                           "insert used token"));
      return;
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      /* UNIQUE constraint violation, meaning this token was already used. */
      TMH_db->rollback (TMH_db->cls);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_CONFLICT,
                                           TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_INVALID,
                                           NULL));
      return;
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      /* Good, proceed! */
      break;
    }
  } /* for all tokens */

  {
    enum GNUNET_DB_QueryStatus qs;

    /* Check if some of these coins already succeeded for _this_ contract.  */
    qs = TMH_db->lookup_deposits (TMH_db->cls,
                                  instance_id,
                                  &pc->check_contract.h_contract_terms,
                                  &check_coin_paid,
                                  pc);
    if (0 > qs)
    {
      TMH_db->rollback (TMH_db->cls);
      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
        return; /* do it again */
      /* Always report on hard error as well to enable diagnostics */
      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
                                           "lookup deposits"));
      return;
    }
    if (pc->pay_transaction.deposit_currency_mismatch)
    {
      TMH_db->rollback (TMH_db->cls);
      GNUNET_break_op (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_BAD_REQUEST,
                                           TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
                                           pc->validate_tokens.brutto.currency))
      ;
      return;
    }
  }

  {
    enum GNUNET_DB_QueryStatus qs;

    /* Check if we refunded some of the coins */
    qs = TMH_db->lookup_refunds (TMH_db->cls,
                                 instance_id,
                                 &pc->check_contract.h_contract_terms,
                                 &check_coin_refunded,
                                 pc);
    if (0 > qs)
    {
      TMH_db->rollback (TMH_db->cls);
      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
        return; /* do it again */
      /* Always report on hard error as well to enable diagnostics */
      GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
                                           "lookup refunds"));
      return;
    }
    if (pc->pay_transaction.refund_currency_mismatch)
    {
      TMH_db->rollback (TMH_db->cls);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_GENERIC_DB_FETCH_FAILED,
                                           "refund currency in database does not match order currency"));
      return;
    }
  }

  /* Check if there are coins that still need to be processed */
  if (0 != pc->pay_transaction.pending)
  {
    /* we made no DB changes, so we can just rollback */
    TMH_db->rollback (TMH_db->cls);
    /* Ok, we need to first go to the network to process more coins.
       We that interaction in *tiny* transactions (hence the rollback
       above). */
    pc->phase = PP_BATCH_DEPOSITS;
    return;
  }

  /* 0 == pc->pay_transaction.pending: all coins processed, let's see if that was enough */
  if (! check_payment_sufficient (pc))
  {
    /* check_payment_sufficient() will have queued an error already.
       We need to still abort the transaction. */
    TMH_db->rollback (TMH_db->cls);
    return;
  }
  /* Payment succeeded, save in database */
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Order `%s' (%s) was fully paid\n",
              pc->order_id,
              GNUNET_h2s (&pc->check_contract.h_contract_terms.hash));
  {
    enum GNUNET_DB_QueryStatus qs;

    qs = TMH_db->mark_contract_paid (TMH_db->cls,
                                     instance_id,
                                     &pc->check_contract.h_contract_terms,
                                     pc->parse_pay.session_id,
                                     pc->parse_wallet_data.choice_index);
    if (qs < 0)
    {
      TMH_db->rollback (TMH_db->cls);
      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
        return; /* do it again */
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_GENERIC_DB_STORE_FAILED,
                                           "mark contract paid"));
      return;
    }
  }


  {
    const struct TALER_MERCHANT_ContractChoice *choice =
      &pc->check_contract.contract_terms->details.v1
      .choices[pc->parse_wallet_data.choice_index];

    for (size_t i = 0; i<pc->output_tokens_len; i++)
    {
      unsigned int output_index;
      enum TALER_MERCHANT_ContractOutputType type;

      output_index = pc->output_tokens[i].output_index;
      GNUNET_assert (output_index < choice->outputs_len);
      type = choice->outputs[output_index].type;

      switch (type)
      {
      case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
        /* Well, good luck getting here */
        GNUNET_break (0);
        pay_end (pc,
                 TALER_MHD_reply_with_error (pc->connection,
                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
                                             TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                                             "invalid output type"));
        break;
      case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
        /* We skip output tokens of donation receipts here, as they are handled in the
         * phase_final_output_token_processing() callback from donau */
        break;
      case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
        struct SignedOutputToken *output =
          &pc->output_tokens[i];
        enum GNUNET_DB_QueryStatus qs;

        qs = TMH_db->insert_issued_token (TMH_db->cls,
                                          &pc->check_contract.h_contract_terms,
                                          &output->h_issue,
                                          &output->sig);
        switch (qs)
        {
        case GNUNET_DB_STATUS_HARD_ERROR:
          TMH_db->rollback (TMH_db->cls);
          GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
          pay_end (pc,
                   TALER_MHD_reply_with_error (pc->connection,
                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
                                               TALER_EC_GENERIC_DB_STORE_FAILED,
                                               "insert output token"));
          return;
        case GNUNET_DB_STATUS_SOFT_ERROR:
          /* Serialization failure, retry */
          TMH_db->rollback (TMH_db->cls);
          return;
        case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
          /* UNIQUE constraint violation, meaning this token was already used. */
          TMH_db->rollback (TMH_db->cls);
          pay_end (pc,
                   TALER_MHD_reply_with_error (pc->connection,
                                               MHD_HTTP_INTERNAL_SERVER_ERROR,
                                               TALER_EC_GENERIC_DB_STORE_FAILED,
                                               "duplicate output token"));
          return;
        case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
          break;
        }
        break;
      }
    }
  }

  TMH_notify_order_change (hc->instance,
                           TMH_OSF_CLAIMED | TMH_OSF_PAID,
                           pc->check_contract.contract_terms->timestamp,
                           pc->check_contract.order_serial);
  {
    enum GNUNET_DB_QueryStatus qs;
    json_t *jhook;

    jhook = GNUNET_JSON_PACK (
      GNUNET_JSON_pack_object_incref ("contract_terms",
                                      pc->check_contract.contract_terms_json),
      GNUNET_JSON_pack_string ("order_id",
                               pc->order_id)
      );
    GNUNET_assert (NULL != jhook);
    qs = TMH_trigger_webhook (pc->hc->instance->settings.id,
                              "pay",
                              jhook);
    json_decref (jhook);
    if (qs < 0)
    {
      TMH_db->rollback (TMH_db->cls);
      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
        return; /* do it again */
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_GENERIC_DB_STORE_FAILED,
                                           "failed to trigger webhooks"));
      return;
    }
  }
  {
    enum GNUNET_DB_QueryStatus qs;

    /* Now commit! */
    qs = TMH_db->commit (TMH_db->cls);
    if (0 > qs)
    {
      /* commit failed */
      TMH_db->rollback (TMH_db->cls);
      if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
        return; /* do it again */
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_GENERIC_DB_COMMIT_FAILED,
                                           NULL));
      return;
    }
  }
  pc->phase++;
}


/**
 * Ensures that the expected number of tokens for a @e key
 * are provided as inputs and have valid signatures.
 *
 * @param[in,out] pc payment context we are processing
 * @param family family the tokens should be from
 * @param index number of the input we are handling
 * @param expected_num number of tokens expected
 * @return #GNUNET_YES on success
 */
static enum GNUNET_GenericReturnValue
find_valid_input_tokens (
  struct PayContext *pc,
  const struct TALER_MERCHANT_ContractTokenFamily *family,
  unsigned int index,
  unsigned int expected_num)
{
  unsigned int num_validated = 0;
  struct GNUNET_TIME_Timestamp now
    = GNUNET_TIME_timestamp_get ();
  const struct TALER_MERCHANT_ContractTokenFamilyKey *kig = NULL;

  for (unsigned int j = 0; j < expected_num; j++)
  {
    struct TokenUseConfirmation *tuc
      = &pc->parse_pay.tokens[index + j];
    const struct TALER_MERCHANT_ContractTokenFamilyKey *key = NULL;

    for (unsigned int i = 0; i<family->keys_len; i++)
    {
      const struct TALER_MERCHANT_ContractTokenFamilyKey *ki
        = &family->keys[i];

      if (0 ==
          GNUNET_memcmp (&ki->pub.public_key->pub_key_hash,
                         &tuc->h_issue.hash))
      {
        if (GNUNET_TIME_timestamp_cmp (ki->valid_after,
                                       >,
                                       now) ||
            GNUNET_TIME_timestamp_cmp (ki->valid_before,
                                       <=,
                                       now))
        {
          /* We have a match, but not in the current validity period */
          GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                      "Public key %s currently not valid\n",
                      GNUNET_h2s (&ki->pub.public_key->pub_key_hash));
          kig = ki;
          continue;
        }
        key = ki;
        break;
      }
    }
    if (NULL == key)
    {
      if (NULL != kig)
      {
        char start_str[128];
        char end_str[128];
        char emsg[350];

        GNUNET_snprintf (start_str,
                         sizeof (start_str),
                         "%s",
                         GNUNET_STRINGS_timestamp_to_string (kig->valid_after));
        GNUNET_snprintf (end_str,
                         sizeof (end_str),
                         "%s",
                         GNUNET_STRINGS_timestamp_to_string (kig->valid_before))
        ;
        /* FIXME: use more specific EC */
        GNUNET_snprintf (emsg,
                         sizeof (emsg),
                         "Token is only valid from %s to %s",
                         start_str,
                         end_str);
        pay_end (pc,
                 TALER_MHD_reply_with_error (
                   pc->connection,
                   MHD_HTTP_GONE,
                   TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
                   emsg));
        return GNUNET_NO;
      }
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Input token supplied for public key %s that is not acceptable\n",
                  GNUNET_h2s (&tuc->h_issue.hash));
      GNUNET_break_op (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_BAD_REQUEST,
                 TALER_EC_MERCHANT_GENERIC_TOKEN_KEY_UNKNOWN,
                 NULL));
      return GNUNET_NO;
    }
    if (GNUNET_OK !=
        TALER_token_issue_verify (&tuc->pub,
                                  &key->pub,
                                  &tuc->unblinded_sig))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Input token for public key with valid_after "
                  "`%s' has invalid issue signature\n",
                  GNUNET_TIME_timestamp2s (key->valid_after));
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_BAD_REQUEST,
                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_ISSUE_SIG_INVALID,
                 NULL));
      return GNUNET_NO;
    }

    if (GNUNET_OK !=
        TALER_wallet_token_use_verify (&pc->check_contract.h_contract_terms,
                                       &pc->parse_wallet_data.h_wallet_data,
                                       &tuc->pub,
                                       &tuc->sig))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Input token for public key with valid_before "
                  "`%s' has invalid use signature\n",
                  GNUNET_TIME_timestamp2s (key->valid_before));
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_BAD_REQUEST,
                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_USE_SIG_INVALID,
                 NULL));
      return GNUNET_NO;
    }

    num_validated++;
  }

  if (num_validated != expected_num)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Expected %d tokens for family %s, but found %d\n",
                expected_num,
                family->slug,
                num_validated);
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_BAD_REQUEST,
               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_COUNT_MISMATCH,
               NULL));
    return GNUNET_NO;
  }
  return GNUNET_YES;
}


/**
 * Check if an output token of the given @a tfk is mandatory, or if
 * wallets are allowed to simply not support it and still proceed.
 *
 * @param tfk token family kind to check
 * @return true if such outputs are mandatory and wallets must supply
 *  the corresponding blinded input
 */
/* FIXME: this function belongs into a lower-level lib! */
static bool
test_tfk_mandatory (enum TALER_MERCHANTDB_TokenFamilyKind tfk)
{
  switch (tfk)
  {
  case TALER_MERCHANTDB_TFK_Discount:
    return false;
  case TALER_MERCHANTDB_TFK_Subscription:
    return true;
  }
  GNUNET_break (0);
  return false;
}


/**
 * Sign the tokens provided by the wallet for a particular @a key.
 *
 * @param[in,out] pc reference for payment we are processing
 * @param key token family data
 * @param priv private key to use to sign with
 * @param mandatory true if the token must exist, if false
 *        and the client did not provide an envelope, that's OK and
 *        we just also skimp on the signature
 * @param index offset in the token envelope array (from other families)
 * @param expected_num number of tokens of this type that we should create
 * @return #GNUNET_NO on failure
 *         #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
sign_token_envelopes (
  struct PayContext *pc,
  const struct TALER_MERCHANT_ContractTokenFamilyKey *key,
  const struct TALER_TokenIssuePrivateKey *priv,
  bool mandatory,
  unsigned int index,
  unsigned int expected_num)
{
  unsigned int num_signed = 0;

  for (unsigned int j = 0; j<expected_num; j++)
  {
    unsigned int pos = index + j;
    const struct TokenEnvelope *env
      = &pc->parse_wallet_data.token_envelopes[pos];
    struct SignedOutputToken *output
      = &pc->output_tokens[pos];

    if ( (pos >= pc->parse_wallet_data.token_envelopes_cnt) ||
         (pos >= pc->output_tokens_len) )
    {
      GNUNET_assert (0); /* this should not happen */
      return GNUNET_NO;
    }
    if (NULL == env->blinded_token.blinded_pub)
    {
      if (! mandatory)
        continue;

      /* mandatory token families require a token envelope. */
      GNUNET_break_op (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_BAD_REQUEST,
                 TALER_EC_GENERIC_PARAMETER_MALFORMED,
                 "Token envelope for mandatory token family missing"));
      return GNUNET_NO;
    }
    TALER_token_issue_sign (priv,
                            &env->blinded_token,
                            &output->sig);
    output->h_issue.hash
      = key->pub.public_key->pub_key_hash;
    num_signed++;
  }

  if (mandatory &&
      (num_signed != expected_num) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Expected %d token envelopes for public key with valid_after "
                "'%s', but found %d\n",
                expected_num,
                GNUNET_TIME_timestamp2s (key->valid_after),
                num_signed);
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_BAD_REQUEST,
               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_TOKEN_ENVELOPE_COUNT_MISMATCH,
               NULL));
    return GNUNET_NO;
  }

  return GNUNET_OK;
}


/**
 * Find the family entry for the family of the given @a slug
 * in @a pc.
 *
 * @param[in] pc payment context to search
 * @param slug slug to search for
 * @return NULL if @a slug was not found
 */
static const struct TALER_MERCHANT_ContractTokenFamily *
find_family (const struct PayContext *pc,
             const char *slug)
{
  for (unsigned int i = 0;
       i < pc->check_contract.contract_terms->details.v1.token_authorities_len;
       i++)
  {
    const struct TALER_MERCHANT_ContractTokenFamily *tfi
      = &pc->check_contract.contract_terms->details.v1.token_authorities[i];

    if (0 == strcmp (tfi->slug,
                     slug))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Token family %s found with %u keys\n",
                  slug,
                  tfi->keys_len);
      return tfi;
    }
  }
  return NULL;
}


/**
 * Handle contract output of type TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN.
 * Looks up the token family, loads the matching private key,
 * and signs the corresponding token envelopes from the wallet.
 *
 * @param pc context for the pay request
 * @param output contract output we need to process
 * @param output_index index of this output in the contract's outputs array
 * @return #GNUNET_OK on success, #GNUNET_NO if an error was encountered
 */
static enum GNUNET_GenericReturnValue
handle_output_token (struct PayContext *pc,
                     const struct TALER_MERCHANT_ContractOutput *output,
                     unsigned int output_index)
{
  const struct TALER_MERCHANT_ContractTokenFamily *family;
  struct TALER_MERCHANT_ContractTokenFamilyKey *key;
  struct TALER_MERCHANTDB_TokenFamilyKeyDetails details;
  enum GNUNET_DB_QueryStatus qs;
  bool mandatory;

  /* Locate token family in the contract.
     This should ever fail as this invariant should
     have been checked when the contract was created. */
  family = find_family (pc,
                        output->details.token.token_family_slug);
  if (NULL == family)
  {
    /* This "should never happen", so treat it as an internal error */
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_INTERNAL_SERVER_ERROR,
               TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
               "token family not found in order"));
    return GNUNET_SYSERR;
  }

  /* Check the key_index field from the output. */
  if (output->details.token.key_index >= family->keys_len)
  {
    /* Also "should never happen", contract was presumably validated on insert */
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_INTERNAL_SERVER_ERROR,
               TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
               "key index invalid for token family"));
    return GNUNET_SYSERR;
  }

  /* Pick the correct key inside that family. */
  key = &family->keys[output->details.token.key_index];

  /* Fetch the private key from the DB for the merchant instance and
   * this particular family/time interval. */
  qs = TMH_db->lookup_token_family_key (
    TMH_db->cls,
    pc->hc->instance->settings.id,
    family->slug,
    pc->check_contract.contract_terms->timestamp,
    pc->check_contract.contract_terms->pay_deadline,
    &details);
  switch (qs)
  {
  case GNUNET_DB_STATUS_HARD_ERROR:
  case GNUNET_DB_STATUS_SOFT_ERROR:
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Database error looking up token-family key for %s\n",
                family->slug);
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_INTERNAL_SERVER_ERROR,
               TALER_EC_GENERIC_DB_FETCH_FAILED,
               NULL));
    return GNUNET_NO;
  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    GNUNET_log (
      GNUNET_ERROR_TYPE_ERROR,
      "Token-family key for %s not found at [%llu,%llu]\n",
      family->slug,
      (unsigned long long)
      pc->check_contract.contract_terms->timestamp.abs_time.abs_value_us,
      (unsigned long long)
      pc->check_contract.contract_terms->pay_deadline.abs_time.abs_value_us
      );
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_NOT_FOUND,
               TALER_EC_GENERIC_DB_FETCH_FAILED,
               NULL));
    return GNUNET_NO;

  case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    break;
  }
  GNUNET_assert (NULL != details.priv.private_key);
  GNUNET_free (details.token_family.slug);
  GNUNET_free (details.token_family.name);
  GNUNET_free (details.token_family.description);
  json_decref (details.token_family.description_i18n);
  GNUNET_CRYPTO_blind_sign_pub_decref (details.pub.public_key);
  GNUNET_free (details.token_family.cipher_spec);

  /* Depending on the token family, decide if the token envelope
   * is mandatory or optional.  (Simplified logic here: adapt as needed.) */
  mandatory = test_tfk_mandatory (details.token_family.kind);
  /* Actually sign the number of token envelopes specified in 'count'.
   * 'output_index' is the offset into the parse_wallet_data arrays.  */
  if (GNUNET_OK !=
      sign_token_envelopes (pc,
                            key,
                            &details.priv,
                            mandatory,
                            output_index,
                            output->details.token.count))
  {
    /* sign_token_envelopes() already queued up an error via pay_end() */
    GNUNET_break_op (0);
    return GNUNET_NO;
  }
  GNUNET_CRYPTO_blind_sign_priv_decref (details.priv.private_key);
  return GNUNET_OK;
}


#ifdef HAVE_DONAU_DONAU_SERVICE_H
/**
 * Handle checks for contract output of type TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT.
 * For now, this does nothing and simply returns #GNUNET_OK.
 *
 * @param pc context for the pay request
 * @param output the contract output describing the donation receipt requirement
 * @return #GNUNET_OK on success,
 *         #GNUNET_NO if an error was already queued
 */
static enum GNUNET_GenericReturnValue
handle_output_donation_receipt (
  struct PayContext *pc,
  const struct TALER_MERCHANT_ContractOutput *output)
{
  enum GNUNET_GenericReturnValue ret;

  ret = DONAU_get_donation_amount_from_bkps (
    pc->parse_wallet_data.donau_keys,
    pc->parse_wallet_data.bkps,
    pc->parse_wallet_data.num_bkps,
    pc->parse_wallet_data.donau.donation_year,
    &pc->parse_wallet_data.donation_amount);
  switch (ret)
  {
  case GNUNET_SYSERR:
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_INTERNAL_SERVER_ERROR,
               TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
               NULL));
    return GNUNET_NO;
  case GNUNET_NO:
    GNUNET_break_op (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_BAD_REQUEST,
               TALER_EC_GENERIC_PARAMETER_MALFORMED,
               "inconsistent bkps / donau keys"));
    return GNUNET_NO;
  case GNUNET_OK:
    break;
  }

  if (GNUNET_OK !=
      TALER_amount_cmp_currency (&pc->parse_wallet_data.donation_amount,
                                 &output->details.donation_receipt.amount))
  {
    GNUNET_break_op (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_BAD_REQUEST,
               TALER_EC_GENERIC_CURRENCY_MISMATCH,
               output->details.donation_receipt.amount.currency));
    return GNUNET_NO;
  }

  if (0 !=
      TALER_amount_cmp (&pc->parse_wallet_data.donation_amount,
                        &output->details.donation_receipt.amount))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Wallet amount: %s\n",
                TALER_amount2s (&pc->parse_wallet_data.donation_amount));
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Donation receipt amount: %s\n",
                TALER_amount2s (&output->details.donation_receipt.amount));
    GNUNET_break_op (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_CONFLICT,
               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DONATION_AMOUNT_MISMATCH,
               "donation amount mismatch"));
    return GNUNET_NO;
  }
  {
    struct TALER_Amount receipts_to_date;

    if (0 >
        TALER_amount_add (&receipts_to_date,
                          &pc->parse_wallet_data.charity_receipts_to_date,
                          &pc->parse_wallet_data.donation_amount))
    {
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_INTERNAL_SERVER_ERROR,
                                           TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
                                           "adding donation amount"));
      return GNUNET_NO;
    }

    if (1 ==
        TALER_amount_cmp (&receipts_to_date,
                          &pc->parse_wallet_data.charity_max_per_year))
    {
      GNUNET_break_op (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (pc->connection,
                                           MHD_HTTP_CONFLICT,
                                           TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DONATION_AMOUNT_MISMATCH,
                                           "donation limit exceeded"));
      return GNUNET_NO;
    }
    pc->parse_wallet_data.charity_receipts_to_date = receipts_to_date;
  }
  return GNUNET_OK;
}


#endif /* HAVE_DONAU_DONAU_SERVICE_H */


/**
 * Count tokens produced by an output.
 *
 * @param pc pay context
 * @param output output to consider
 * @returns number of output tokens
 */
static unsigned int
count_output_tokens (const struct PayContext *pc,
                     const struct TALER_MERCHANT_ContractOutput *output)
{
  switch (output->type)
  {
  case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
    GNUNET_assert (0);
    break;
  case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
    return output->details.token.count;
  case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
#ifdef HAVE_DONAU_DONAU_SERVICE_H
    return pc->parse_wallet_data.num_bkps;
#else
    return 0;
#endif
  }
  /* Not reached. */
  GNUNET_assert (0);
}


/**
 * Validate tokens and token envelopes. First, we check if all tokens listed
 * in the 'inputs' array of the selected choice are present in the 'tokens'
 * array of the request. Then, we validate the signatures of each provided
 * token.
 *
 * @param[in,out] pc context we use to handle the payment
 */
static void
phase_validate_tokens (struct PayContext *pc)
{
  /* We haven't seen a donau output yet. */
  pc->validate_tokens.donau_output_index = -1;

  switch (pc->check_contract.contract_terms->version)
  {
  case TALER_MERCHANT_CONTRACT_VERSION_0:
    /* No tokens to validate */
    pc->phase = PP_PAY_TRANSACTION;
    pc->validate_tokens.max_fee
      = pc->check_contract.contract_terms->details.v0.max_fee;
    pc->validate_tokens.brutto
      = pc->check_contract.contract_terms->details.v0.brutto;
    break;
  case TALER_MERCHANT_CONTRACT_VERSION_1:
    {
      const struct TALER_MERCHANT_ContractChoice *selected
        = &pc->check_contract.contract_terms->details.v1.choices[
            pc->parse_wallet_data.choice_index];
      unsigned int output_off;
      unsigned int cnt;

      pc->validate_tokens.max_fee = selected->max_fee;
      pc->validate_tokens.brutto = selected->amount;

      for (unsigned int i = 0; i<selected->inputs_len; i++)
      {
        const struct TALER_MERCHANT_ContractInput *input
          = &selected->inputs[i];
        const struct TALER_MERCHANT_ContractTokenFamily *family;

        switch (input->type)
        {
        case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
          GNUNET_break (0);
          pay_end (pc,
                   TALER_MHD_reply_with_error (
                     pc->connection,
                     MHD_HTTP_BAD_REQUEST,
                     TALER_EC_GENERIC_PARAMETER_MALFORMED,
                     "input token type not valid"));
          return;
#if FUTURE
        case TALER_MERCHANT_CONTRACT_INPUT_TYPE_COIN:
          GNUNET_break (0);
          pay_end (pc,
                   TALER_MHD_reply_with_error (
                     pc->connection,
                     MHD_HTTP_NOT_IMPLEMENTED,
                     TALER_EC_MERCHANT_GENERIC_FEATURE_NOT_AVAILABLE,
                     "token type not yet supported"));
          return;
#endif
        case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
          family = find_family (pc,
                                input->details.token.token_family_slug);
          if (NULL == family)
          {
            /* this should never happen, since the choices and
               token families are validated on insert. */
            GNUNET_break (0);
            pay_end (pc,
                     TALER_MHD_reply_with_error (
                       pc->connection,
                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                       "token family not found in order"));
            return;
          }
          if (GNUNET_NO ==
              find_valid_input_tokens (pc,
                                       family,
                                       i,
                                       input->details.token.count))
          {
            /* Error is already scheduled from find_valid_input_token. */
            return;
          }
        }
      }

      /* calculate pc->output_tokens_len */
      output_off = 0;
      for (unsigned int i = 0; i<selected->outputs_len; i++)
      {
        const struct TALER_MERCHANT_ContractOutput *output
          = &selected->outputs[i];

        switch (output->type)
        {
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
          GNUNET_assert (0);
          break;
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
          cnt = output->details.token.count;
          if (output_off + cnt < output_off)
          {
            GNUNET_break_op (0);
            pay_end (pc,
                     TALER_MHD_reply_with_error (
                       pc->connection,
                       MHD_HTTP_BAD_REQUEST,
                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
                       "output token counter overflow"));
            return;
          }
          output_off += cnt;
          break;
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
          /* check that this output type appears at most once */
          if (pc->validate_tokens.donau_output_index >= 0)
          {
            /* This should have been prevented when the
               contract was initially created */
            GNUNET_break (0);
            pay_end (pc,
                     TALER_MHD_reply_with_error (
                       pc->connection,
                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                       TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
                       "two donau output sets in same contract"));
            return;
          }
          pc->validate_tokens.donau_output_index = i;
#ifdef HAVE_DONAU_DONAU_SERVICE_H
          if (output_off + pc->parse_wallet_data.num_bkps < output_off)
          {
            GNUNET_break_op (0);
            pay_end (pc,
                     TALER_MHD_reply_with_error (
                       pc->connection,
                       MHD_HTTP_BAD_REQUEST,
                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
                       "output token counter overflow"));
            return;
          }
          output_off += pc->parse_wallet_data.num_bkps;
#endif
          break;
        }
      }


      pc->output_tokens_len = output_off;
      pc->output_tokens
        = GNUNET_new_array (pc->output_tokens_len,
                            struct SignedOutputToken);

      /* calculate pc->output_tokens[].output_index */
      output_off = 0;
      for (unsigned int i = 0; i<selected->outputs_len; i++)
      {
        const struct TALER_MERCHANT_ContractOutput *output
          = &selected->outputs[i];
        cnt = count_output_tokens (pc,
                                   output);
        for (unsigned int j = 0; j<cnt; j++)
          pc->output_tokens[output_off + j].output_index = i;
        output_off += cnt;
      }

      /* compute non-donau outputs */
      output_off = 0;
      for (unsigned int i = 0; i<selected->outputs_len; i++)
      {
        const struct TALER_MERCHANT_ContractOutput *output
          = &selected->outputs[i];

        switch (output->type)
        {
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
          GNUNET_assert (0);
          break;
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
          cnt = output->details.token.count;
          GNUNET_assert (output_off + cnt
                         <= pc->output_tokens_len);
          if (GNUNET_OK !=
              handle_output_token (pc,
                                   output,
                                   output_off))
          {
            /* Error is already scheduled from handle_output_token. */
            return;
          }
          output_off += cnt;
          break;
        case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
#ifndef HAVE_DONAU_DONAU_SERVICE_H
          /* We checked at parse time, and
             wallet didn't want donau, so OK! */
          return;
#else
          if ( (0 != pc->parse_wallet_data.num_bkps) &&
               (GNUNET_OK !=
                handle_output_donation_receipt (pc,
                                                output)) )
          {
            /* Error is already scheduled from handle_output_donation_receipt. */
            return;
          }
          output_off += pc->parse_wallet_data.num_bkps;
          continue;
#endif
        } /* switch on output token */
      } /* for all output token types */
    } /* case contract v1 */
    break;
  } /* switch on contract type */

  for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
  {
    const struct DepositConfirmation *dc = &pc->parse_pay.dc[i];

    if (GNUNET_OK !=
        TALER_amount_cmp_currency (&dc->cdd.amount,
                                   &pc->validate_tokens.brutto))
    {
      GNUNET_break_op (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_CONFLICT,
                 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
                 pc->validate_tokens.brutto.currency));
      return;
    }
  }

  pc->phase = PP_PAY_TRANSACTION;
}


/**
 * Function called with information about a coin that was deposited.
 * Checks if this coin is in our list of deposits as well.
 *
 * @param cls closure with our `struct PayContext *`
 * @param deposit_serial which deposit operation is this about
 * @param exchange_url URL of the exchange that issued the coin
 * @param h_wire hash of merchant's wire details
 * @param deposit_timestamp when was the deposit made
 * @param amount_with_fee amount the exchange will deposit for this coin
 * @param deposit_fee fee the exchange will charge for this coin
 * @param coin_pub public key of the coin
 */
static void
deposit_paid_check (
  void *cls,
  uint64_t deposit_serial,
  const char *exchange_url,
  const struct TALER_MerchantWireHashP *h_wire,
  struct GNUNET_TIME_Timestamp deposit_timestamp,
  const struct TALER_Amount *amount_with_fee,
  const struct TALER_Amount *deposit_fee,
  const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
  struct PayContext *pc = cls;

  for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
  {
    struct DepositConfirmation *dci = &pc->parse_pay.dc[i];

    if ( (0 ==
          GNUNET_memcmp (&dci->cdd.coin_pub,
                         coin_pub)) &&
         (0 ==
          strcmp (dci->exchange_url,
                  exchange_url)) &&
         (GNUNET_YES ==
          TALER_amount_cmp_currency (&dci->cdd.amount,
                                     amount_with_fee)) &&
         (0 ==
          TALER_amount_cmp (&dci->cdd.amount,
                            amount_with_fee)) )
    {
      dci->matched_in_db = true;
      break;
    }
  }
}


/**
 * Function called with information about a token that was spent.
 * FIXME: Replace this with a more specific function for this cb
 *
 * @param cls closure with `struct PayContext *`
 * @param spent_token_serial "serial" of the spent token unused
 * @param h_contract_terms hash of the contract terms unused
 * @param h_issue_pub hash of the token issue public key unused
 * @param use_pub public key of the token
 * @param use_sig signature of the token
 * @param issue_sig signature of the token issue
 */
static void
input_tokens_paid_check (
  void *cls,
  uint64_t spent_token_serial,
  const struct TALER_PrivateContractHashP *h_contract_terms,
  const struct TALER_TokenIssuePublicKeyHashP *h_issue_pub,
  const struct TALER_TokenUsePublicKeyP *use_pub,
  const struct TALER_TokenUseSignatureP *use_sig,
  const struct TALER_TokenIssueSignature *issue_sig)
{
  struct PayContext *pc = cls;

  for (size_t i = 0; i<pc->parse_pay.tokens_cnt; i++)
  {
    struct TokenUseConfirmation *tuc = &pc->parse_pay.tokens[i];

    if ( (0 ==
          GNUNET_memcmp (&tuc->pub,
                         use_pub)) &&
         (0 ==
          GNUNET_memcmp (&tuc->sig,
                         use_sig)) &&
         (0 ==
          GNUNET_memcmp (&tuc->unblinded_sig,
                         issue_sig)) )
    {
      tuc->found_in_db = true;
      break;
    }
  }
}


/**
 * Small helper function to append an output token signature from db
 *
 * @param cls closure with `struct PayContext *`
 * @param h_issue hash of the token
 * @param sig signature of the token
 */
static void
append_output_token_sig (void *cls,
                         struct GNUNET_HashCode *h_issue,
                         struct GNUNET_CRYPTO_BlindedSignature *sig)
{
  struct PayContext *pc = cls;
  struct TALER_MERCHANT_ContractChoice *choice;
  const struct TALER_MERCHANT_ContractOutput *output;
  struct SignedOutputToken out;
  unsigned int cnt;

  GNUNET_assert (TALER_MERCHANT_CONTRACT_VERSION_1 ==
                 pc->check_contract.contract_terms->version);
  choice = &pc->check_contract.contract_terms->details.v1
           .choices[pc->parse_wallet_data.choice_index];
  output = &choice->outputs[pc->output_index_gen];
  cnt = count_output_tokens (pc,
                             output);
  out.output_index = pc->output_index_gen;
  out.h_issue.hash = *h_issue;
  out.sig.signature = sig;
  GNUNET_CRYPTO_blind_sig_incref (sig);
  GNUNET_array_append (pc->output_tokens,
                       pc->output_tokens_len,
                       out);
  /* Go to next output once we've output all tokens for the current one. */
  pc->output_token_cnt++;
  if (pc->output_token_cnt >= cnt)
  {
    pc->output_token_cnt = 0;
    pc->output_index_gen++;
  }
}


/**
 * Handle case where contract was already paid. Either decides
 * the payment is idempotent, or refunds the excess payment.
 *
 * @param[in,out] pc context we use to handle the payment
 */
static void
phase_contract_paid (struct PayContext *pc)
{
  json_t *refunds;
  bool unmatched = false;

  {
    enum GNUNET_DB_QueryStatus qs;

    qs = TMH_db->lookup_deposits_by_order (TMH_db->cls,
                                           pc->check_contract.order_serial,
                                           &deposit_paid_check,
                                           pc);
    /* Since orders with choices can have a price of zero,
       0 is also a valid query state */
    if (qs < 0)
    {
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_INTERNAL_SERVER_ERROR,
                 TALER_EC_GENERIC_DB_FETCH_FAILED,
                 "lookup_deposits_by_order"));
      return;
    }
  }
  for (size_t i = 0;
       i<pc->parse_pay.coins_cnt && ! unmatched;
       i++)
  {
    struct DepositConfirmation *dci = &pc->parse_pay.dc[i];

    if (! dci->matched_in_db)
      unmatched = true;
  }
  /* Check if provided input tokens match token in the database */
  {
    enum GNUNET_DB_QueryStatus qs;

    /* FIXME-Optimization: Maybe use h_contract instead of order_serial here? */
    qs = TMH_db->lookup_spent_tokens_by_order (TMH_db->cls,
                                               pc->check_contract.order_serial,
                                               &input_tokens_paid_check,
                                               pc);

    if (qs < 0)
    {
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_INTERNAL_SERVER_ERROR,
                 TALER_EC_GENERIC_DB_FETCH_FAILED,
                 "lookup_spent_tokens_by_order"));
      return;
    }
  }
  for (size_t i = 0; i<pc->parse_pay.tokens_cnt && ! unmatched; i++)
  {
    struct TokenUseConfirmation *tuc = &pc->parse_pay.tokens[i];

    if (! tuc->found_in_db)
      unmatched = true;
  }

  /* In this part we are fetching token_sigs related output */
  if (! unmatched)
  {
    /* Everything fine, idempotent request, generate response immediately */
    enum GNUNET_DB_QueryStatus qs;

    pc->output_index_gen = 0;
    qs = TMH_db->select_order_blinded_sigs (
      TMH_db->cls,
      pc->order_id,
      &append_output_token_sig,
      pc);
    if (0 > qs)
    {
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_INTERNAL_SERVER_ERROR,
                 TALER_EC_GENERIC_DB_FETCH_FAILED,
                 "select_order_blinded_sigs"));
      return;
    }

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Idempotent pay request for order `%s', signing again\n",
                pc->order_id);
    pc->phase = PP_SUCCESS_RESPONSE;
    return;
  }
  /* Conflict, double-payment detected! */
  /* FIXME-#8674: What should we do with input tokens?
     Currently there is no refund for tokens. */
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Client attempted to pay extra for already paid order `%s'\n",
              pc->order_id);
  refunds = json_array ();
  GNUNET_assert (NULL != refunds);
  for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
  {
    struct DepositConfirmation *dci = &pc->parse_pay.dc[i];
    struct TALER_MerchantSignatureP merchant_sig;

    if (dci->matched_in_db)
      continue;
    TALER_merchant_refund_sign (&dci->cdd.coin_pub,
                                &pc->check_contract.h_contract_terms,
                                0, /* rtransaction id */
                                &dci->cdd.amount,
                                &pc->hc->instance->merchant_priv,
                                &merchant_sig);
    GNUNET_assert (
      0 ==
      json_array_append_new (
        refunds,
        GNUNET_JSON_PACK (
          GNUNET_JSON_pack_data_auto (
            "coin_pub",
            &dci->cdd.coin_pub),
          GNUNET_JSON_pack_data_auto (
            "merchant_sig",
            &merchant_sig),
          TALER_JSON_pack_amount ("amount",
                                  &dci->cdd.amount),
          GNUNET_JSON_pack_uint64 ("rtransaction_id",
                                   0))));
  }
  pay_end (pc,
           TALER_MHD_REPLY_JSON_PACK (
             pc->connection,
             MHD_HTTP_CONFLICT,
             TALER_MHD_PACK_EC (
               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_ALREADY_PAID),
             GNUNET_JSON_pack_array_steal ("refunds",
                                           refunds)));
}


/**
 * Check the database state for the given order.
 * Schedules an error response in the connection on failure.
 *
 * @param[in,out] pc context we use to handle the payment
 */
static void
phase_check_contract (struct PayContext *pc)
{
  /* obtain contract terms */
  enum GNUNET_DB_QueryStatus qs;
  bool paid = false;

  if (NULL != pc->check_contract.contract_terms_json)
  {
    json_decref (pc->check_contract.contract_terms_json);
    pc->check_contract.contract_terms_json = NULL;
  }
  if (NULL != pc->check_contract.contract_terms)
  {
    TALER_MERCHANT_contract_free (pc->check_contract.contract_terms);
    pc->check_contract.contract_terms = NULL;
  }
  qs = TMH_db->lookup_contract_terms2 (TMH_db->cls,
                                       pc->hc->instance->settings.id,
                                       pc->order_id,
                                       &pc->check_contract.contract_terms_json,
                                       &pc->check_contract.order_serial,
                                       &paid,
                                       NULL,
                                       &pc->check_contract.pos_key,
                                       &pc->check_contract.pos_alg);
  if (0 > qs)
  {
    /* single, read-only SQL statements should never cause
       serialization problems */
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
    /* Always report on hard error to enable diagnostics */
    GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_INTERNAL_SERVER_ERROR,
               TALER_EC_GENERIC_DB_FETCH_FAILED,
               "contract terms"));
    return;
  }
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
  {
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_NOT_FOUND,
               TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN,
               pc->order_id));
    return;
  }
  /* hash contract (needed later) */
#if DEBUG
  json_dumpf (pc->check_contract.contract_terms_json,
              stderr,
              JSON_INDENT (2));
#endif
  if (GNUNET_OK !=
      TALER_JSON_contract_hash (pc->check_contract.contract_terms_json,
                                &pc->check_contract.h_contract_terms))
  {
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_INTERNAL_SERVER_ERROR,
               TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
               NULL));
    return;
  }

  /* Parse the contract terms even for paid orders,
     as later phases need it. */

  pc->check_contract.contract_terms = TALER_MERCHANT_contract_parse (
    pc->check_contract.contract_terms_json,
    true);

  if (NULL == pc->check_contract.contract_terms)
  {
    /* invalid contract */
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_INTERNAL_SERVER_ERROR,
               TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
               pc->order_id));
    return;
  }

  if (paid)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Order `%s' paid, checking for double-payment\n",
                pc->order_id);
    pc->phase = PP_CONTRACT_PAID;
    return;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Handling payment for order `%s' with contract hash `%s'\n",
              pc->order_id,
              GNUNET_h2s (&pc->check_contract.h_contract_terms.hash));

  /* Check fundamentals */
  {
    switch (pc->check_contract.contract_terms->version)
    {
    case TALER_MERCHANT_CONTRACT_VERSION_0:
      {
        if (pc->parse_wallet_data.choice_index > 0)
        {
          GNUNET_break (0);
          pay_end (pc,
                   TALER_MHD_reply_with_error (
                     pc->connection,
                     MHD_HTTP_BAD_REQUEST,
                     TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_OUT_OF_BOUNDS,
                     "contract terms v0 has no choices"));
          return;
        }
      }
      break;
    case TALER_MERCHANT_CONTRACT_VERSION_1:
      {
        if (pc->parse_wallet_data.choice_index < 0)
        {
          GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                      "Order `%s' has non-empty choices array but"
                      "request is missing 'choice_index' field\n",
                      pc->order_id);
          GNUNET_break (0);
          pay_end (pc,
                   TALER_MHD_reply_with_error (
                     pc->connection,
                     MHD_HTTP_BAD_REQUEST,
                     TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_MISSING,
                     NULL));
          return;
        }
        if (pc->parse_wallet_data.choice_index >=
            pc->check_contract.contract_terms->details.v1.choices_len)
        {
          GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                      "Order `%s' has choices array with %u elements but "
                      "request has 'choice_index' field with value %d\n",
                      pc->order_id,
                      pc->check_contract.contract_terms->details.v1.choices_len,
                      pc->parse_wallet_data.choice_index);
          GNUNET_break (0);
          pay_end (pc,
                   TALER_MHD_reply_with_error (
                     pc->connection,
                     MHD_HTTP_BAD_REQUEST,
                     TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_CHOICE_INDEX_OUT_OF_BOUNDS,
                     NULL));
          return;
        }
      }
      break;
    default:
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_INTERNAL_SERVER_ERROR,
                 TALER_EC_GENERIC_DB_FETCH_FAILED,
                 "contract 'version' in database not supported by this backend")
               );
      return;
    }
  }

  if (GNUNET_TIME_timestamp_cmp (pc->check_contract.contract_terms->
                                 wire_deadline,
                                 <,
                                 pc->check_contract.contract_terms->
                                 refund_deadline))
  {
    /* This should already have been checked when creating the order! */
    GNUNET_break (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_INTERNAL_SERVER_ERROR,
               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_REFUND_DEADLINE_PAST_WIRE_TRANSFER_DEADLINE,
               NULL));
    return;
  }
  if (GNUNET_TIME_absolute_is_past (pc->check_contract.contract_terms->
                                    pay_deadline.abs_time))
  {
    /* too late */
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_GONE,
               TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_OFFER_EXPIRED,
               NULL));
    return;
  }

/* Make sure wire method (still) exists for this instance */
  {
    struct TMH_WireMethod *wm;

    wm = pc->hc->instance->wm_head;
    while (0 != GNUNET_memcmp (&pc->check_contract.contract_terms->h_wire,
                               &wm->h_wire))
      wm = wm->next;
    if (NULL == wm)
    {
      GNUNET_break (0);
      pay_end (pc,
               TALER_MHD_reply_with_error (
                 pc->connection,
                 MHD_HTTP_INTERNAL_SERVER_ERROR,
                 TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_WIRE_HASH_UNKNOWN,
                 NULL));
      return;
    }
    pc->check_contract.wm = wm;
  }
  pc->phase = PP_VALIDATE_TOKENS;
}


/**
 * Try to parse the wallet_data object of the pay request into
 * the given context. Schedules an error response in the connection
 * on failure.
 *
 * @param[in,out] pc context we use to handle the payment
 */
static void
phase_parse_wallet_data (struct PayContext *pc)
{
  const json_t *tokens_evs;
  const json_t *donau_obj;

  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_int16 ("choice_index",
                              &pc->parse_wallet_data.choice_index),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("tokens_evs",
                                    &tokens_evs),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("donau",
                                     &donau_obj),
      NULL),
    GNUNET_JSON_spec_end ()
  };

  pc->parse_wallet_data.choice_index = -1;
  if (NULL == pc->parse_pay.wallet_data)
  {
    pc->phase = PP_CHECK_CONTRACT;
    return;
  }
  {
    enum GNUNET_GenericReturnValue res;

    res = TALER_MHD_parse_json_data (pc->connection,
                                     pc->parse_pay.wallet_data,
                                     spec);
    if (GNUNET_YES != res)
    {
      GNUNET_break_op (0);
      pay_end (pc,
               (GNUNET_NO == res)
             ? MHD_YES
             : MHD_NO);
      return;
    }
  }

  pc->parse_wallet_data.token_envelopes_cnt
    = json_array_size (tokens_evs);
  if (pc->parse_wallet_data.token_envelopes_cnt >
      MAX_TOKEN_ALLOWED_OUTPUTs)
  {
    GNUNET_break_op (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_BAD_REQUEST,
               TALER_EC_GENERIC_PARAMETER_MALFORMED,
               "'tokens_evs' array too long"));
    return;
  }
  pc->parse_wallet_data.token_envelopes
    = GNUNET_new_array (pc->parse_wallet_data.token_envelopes_cnt,
                        struct TokenEnvelope);

  {
    unsigned int tokens_ev_index;
    json_t *token_ev;

    json_array_foreach (tokens_evs,
                        tokens_ev_index,
                        token_ev)
    {
      struct TokenEnvelope *ev
        = &pc->parse_wallet_data.token_envelopes[tokens_ev_index];
      struct GNUNET_JSON_Specification ispec[] = {
        TALER_JSON_spec_token_envelope (NULL,
                                        &ev->blinded_token),
        GNUNET_JSON_spec_end ()
      };
      enum GNUNET_GenericReturnValue res;

      if (json_is_null (token_ev))
        continue;
      res = TALER_MHD_parse_json_data (pc->connection,
                                       token_ev,
                                       ispec);
      if (GNUNET_YES != res)
      {
        GNUNET_break_op (0);
        pay_end (pc,
                 (GNUNET_NO == res)
                 ? MHD_YES
                 : MHD_NO);
        return;
      }

      for (unsigned int j = 0; j<tokens_ev_index; j++)
      {
        if (0 ==
            GNUNET_memcmp (ev->blinded_token.blinded_pub,
                           pc->parse_wallet_data.token_envelopes[j].
                           blinded_token.blinded_pub))
        {
          GNUNET_break_op (0);
          pay_end (pc,
                   TALER_MHD_reply_with_error (
                     pc->connection,
                     MHD_HTTP_BAD_REQUEST,
                     TALER_EC_GENERIC_PARAMETER_MALFORMED,
                     "duplicate token envelope in list"));
          return;
        }
      }
    }
  }

#ifdef HAVE_DONAU_DONAU_SERVICE_H
  if (NULL != donau_obj)
  {
    const char *donau_url_tmp;
    const json_t *budikeypairs;
    json_t *donau_keys_json;

    /* Fetching and checking that all 3 are present in some way */
    struct GNUNET_JSON_Specification dspec[] = {
      GNUNET_JSON_spec_string      ("url",
                                    &donau_url_tmp),
      GNUNET_JSON_spec_uint64      ("year",
                                    &pc->parse_wallet_data.donau.donation_year),
      GNUNET_JSON_spec_array_const ("budikeypairs",
                                    &budikeypairs),
      GNUNET_JSON_spec_end ()
    };
    enum GNUNET_GenericReturnValue res;

    res = TALER_MHD_parse_json_data (pc->connection,
                                     donau_obj,
                                     dspec);
    if (GNUNET_YES != res)
    {
      GNUNET_break_op (0);
      pay_end (pc,
               (GNUNET_NO == res)
               ? MHD_YES
               : MHD_NO);
      return;
    }

    /* Check if the needed data is present for the given donau URL */
    {
      enum GNUNET_DB_QueryStatus qs;

      qs = TMH_db->lookup_order_charity (
        TMH_db->cls,
        pc->hc->instance->settings.id,
        donau_url_tmp,
        &pc->parse_wallet_data.charity_id,
        &pc->parse_wallet_data.charity_priv,
        &pc->parse_wallet_data.charity_max_per_year,
        &pc->parse_wallet_data.charity_receipts_to_date,
        &donau_keys_json,
        &pc->parse_wallet_data.donau_instance_serial);

      switch (qs)
      {
      case GNUNET_DB_STATUS_HARD_ERROR:
      case GNUNET_DB_STATUS_SOFT_ERROR:
        TMH_db->rollback (TMH_db->cls);
        pay_end (pc,
                 TALER_MHD_reply_with_error (
                   pc->connection,
                   MHD_HTTP_INTERNAL_SERVER_ERROR,
                   TALER_EC_GENERIC_DB_FETCH_FAILED,
                   "lookup_order_charity"));
        return;
      case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
        TMH_db->rollback (TMH_db->cls);
        pay_end (pc,
                 TALER_MHD_reply_with_error (
                   pc->connection,
                   MHD_HTTP_NOT_FOUND,
                   TALER_EC_GENERIC_PARAMETER_MALFORMED,
                   "No matching Donau charity found for the given URL"));
        return;
      case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
        pc->parse_wallet_data.donau.donau_url =
          GNUNET_strdup (donau_url_tmp);
        break;
      }
    }

    {
      pc->parse_wallet_data.donau_keys =
        DONAU_keys_from_json (donau_keys_json);
      json_decref (donau_keys_json);
      if (NULL == pc->parse_wallet_data.donau_keys)
      {
        GNUNET_break_op (0);
        pay_end (pc,
                 TALER_MHD_reply_with_error (pc->connection,
                                             MHD_HTTP_BAD_REQUEST,
                                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                             "Invalid donau_keys"));
        return;
      }
    }

    /* Stage to parse the budikeypairs from json to struct */
    if (0 != json_array_size (budikeypairs))
    {
      size_t num_bkps = json_array_size (budikeypairs);
      struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps =
        GNUNET_new_array (num_bkps,
                          struct DONAU_BlindedUniqueDonorIdentifierKeyPair);

      /* Change to json for each*/
      for (size_t i = 0; i < num_bkps; i++)
      {
        const json_t *bkp_obj = json_array_get (budikeypairs,
                                                i);
        if (GNUNET_SYSERR ==
            merchant_parse_json_bkp (&bkps[i],
                                     bkp_obj))
        {
          GNUNET_break_op (0);
          for (size_t j = 0; i < j; j++)
            GNUNET_CRYPTO_blinded_message_decref (
              bkps[j].blinded_udi.blinded_message);
          GNUNET_free (bkps);
          pay_end (pc,
                   TALER_MHD_reply_with_error (pc->connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                               "Failed to parse budikeypairs"));
          return;
        }
      }

      pc->parse_wallet_data.num_bkps = num_bkps;
      pc->parse_wallet_data.bkps = bkps;
    }
  }
#else
  /* Donau not compiled in: reject request if a donau object was given. */
  if (NULL != donau_obj)
  {
    pay_end (pc,
             TALER_MHD_reply_with_error (pc->connection,
                                         MHD_HTTP_NOT_IMPLEMENTED,
                                         TALER_EC_MERCHANT_GENERIC_DONAU_NOT_CONFIGURED,
                                         "donau support disabled"));
    return;
  }
#endif /* HAVE_DONAU_DONAU_SERVICE_H */

  TALER_json_hash (pc->parse_pay.wallet_data,
                   &pc->parse_wallet_data.h_wallet_data);

  pc->phase = PP_CHECK_CONTRACT;
}


/**
 * Try to parse the pay request into the given pay context.
 * Schedules an error response in the connection on failure.
 *
 * @param[in,out] pc context we use to handle the payment
 */
static void
phase_parse_pay (struct PayContext *pc)
{
  const char *session_id = NULL;
  const json_t *coins;
  const json_t *tokens;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_array_const ("coins",
                                  &coins),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_string ("session_id",
                               &session_id),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("wallet_data",
                                     &pc->parse_pay.wallet_data),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_array_const ("tokens",
                                    &tokens),
      NULL),
    GNUNET_JSON_spec_end ()
  };

#if DEBUG
  {
    char *dump = json_dumps (pc->hc->request_body,
                             JSON_INDENT (2)
                             | JSON_ENCODE_ANY
                             | JSON_SORT_KEYS);

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "POST /orders/%s/pay – request body follows:\n%s\n",
                pc->order_id,
                dump);

    free (dump);

  }
#endif /* DEBUG */

  GNUNET_assert (PP_PARSE_PAY == pc->phase);
  {
    enum GNUNET_GenericReturnValue res;

    res = TALER_MHD_parse_json_data (pc->connection,
                                     pc->hc->request_body,
                                     spec);
    if (GNUNET_YES != res)
    {
      GNUNET_break_op (0);
      pay_end (pc,
               (GNUNET_NO == res)
               ? MHD_YES
               : MHD_NO);
      return;
    }
  }

  /* copy session ID (if set) */
  if (NULL != session_id)
  {
    pc->parse_pay.session_id = GNUNET_strdup (session_id);
  }
  else
  {
    /* use empty string as default if client didn't specify it */
    pc->parse_pay.session_id = GNUNET_strdup ("");
  }

  pc->parse_pay.coins_cnt = json_array_size (coins);
  if (pc->parse_pay.coins_cnt > MAX_COIN_ALLOWED_COINS)
  {
    GNUNET_break_op (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_BAD_REQUEST,
               TALER_EC_GENERIC_PARAMETER_MALFORMED,
               "'coins' array too long"));
    return;
  }
  /* note: 1 coin = 1 deposit confirmation expected */
  pc->parse_pay.dc = GNUNET_new_array (pc->parse_pay.coins_cnt,
                                       struct DepositConfirmation);

  /* This loop populates the array 'dc' in 'pc' */
  {
    unsigned int coins_index;
    json_t *coin;

    json_array_foreach (coins, coins_index, coin)
    {
      struct DepositConfirmation *dc = &pc->parse_pay.dc[coins_index];
      const char *exchange_url;
      struct GNUNET_JSON_Specification ispec[] = {
        GNUNET_JSON_spec_fixed_auto ("coin_sig",
                                     &dc->cdd.coin_sig),
        GNUNET_JSON_spec_fixed_auto ("coin_pub",
                                     &dc->cdd.coin_pub),
        TALER_JSON_spec_denom_sig ("ub_sig",
                                   &dc->cdd.denom_sig),
        GNUNET_JSON_spec_fixed_auto ("h_denom",
                                     &dc->cdd.h_denom_pub),
        TALER_JSON_spec_amount_any ("contribution",
                                    &dc->cdd.amount),
        TALER_JSON_spec_web_url ("exchange_url",
                                 &exchange_url),
        /* if a minimum age was required, the minimum_age_sig and
         * age_commitment must be provided */
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_fixed_auto ("minimum_age_sig",
                                       &dc->minimum_age_sig),
          &dc->no_minimum_age_sig),
        GNUNET_JSON_spec_mark_optional (
          TALER_JSON_spec_age_commitment ("age_commitment",
                                          &dc->age_commitment),
          &dc->no_age_commitment),
        /* if minimum age was not required, but coin with age restriction set
         * was used, h_age_commitment must be provided. */
        GNUNET_JSON_spec_mark_optional (
          GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
                                       &dc->cdd.h_age_commitment),
          &dc->no_h_age_commitment),
        GNUNET_JSON_spec_end ()
      };
      enum GNUNET_GenericReturnValue res;
      struct ExchangeGroup *eg = NULL;

      res = TALER_MHD_parse_json_data (pc->connection,
                                       coin,
                                       ispec);
      if (GNUNET_YES != res)
      {
        GNUNET_break_op (0);
        pay_end (pc,
                 (GNUNET_NO == res)
                 ? MHD_YES
                 : MHD_NO);
        return;
      }
      for (unsigned int j = 0; j<coins_index; j++)
      {
        if (0 ==
            GNUNET_memcmp (&dc->cdd.coin_pub,
                           &pc->parse_pay.dc[j].cdd.coin_pub))
        {
          GNUNET_break_op (0);
          pay_end (pc,
                   TALER_MHD_reply_with_error (pc->connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                               "duplicate coin in list"));
          return;
        }
      }

      dc->exchange_url = GNUNET_strdup (exchange_url);
      dc->index = coins_index;
      dc->pc = pc;

      /* Check the consistency of the (potential) age restriction
       * information. */
      if (dc->no_age_commitment != dc->no_minimum_age_sig)
      {
        GNUNET_break_op (0);
        pay_end (pc,
                 TALER_MHD_reply_with_error (
                   pc->connection,
                   MHD_HTTP_BAD_REQUEST,
                   TALER_EC_GENERIC_PARAMETER_MALFORMED,
                   "inconsistent: 'age_commitment' vs. 'minimum_age_sig'"
                   ));
        return;
      }

      /* Setup exchange group */
      for (unsigned int i = 0; i<pc->parse_pay.num_exchanges; i++)
      {
        if (0 ==
            strcmp (pc->parse_pay.egs[i]->exchange_url,
                    exchange_url))
        {
          eg = pc->parse_pay.egs[i];
          break;
        }
      }
      if (NULL == eg)
      {
        eg = GNUNET_new (struct ExchangeGroup);
        eg->pc = pc;
        eg->exchange_url = dc->exchange_url;
        eg->total = dc->cdd.amount;
        GNUNET_array_append (pc->parse_pay.egs,
                             pc->parse_pay.num_exchanges,
                             eg);
      }
      else
      {
        if (0 >
            TALER_amount_add (&eg->total,
                              &eg->total,
                              &dc->cdd.amount))
        {
          GNUNET_break_op (0);
          pay_end (pc,
                   TALER_MHD_reply_with_error (
                     pc->connection,
                     MHD_HTTP_INTERNAL_SERVER_ERROR,
                     TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
                     "Overflow adding up amounts"));
          return;
        }
      }
    }
  }

  pc->parse_pay.tokens_cnt = json_array_size (tokens);
  if (pc->parse_pay.tokens_cnt > MAX_TOKEN_ALLOWED_INPUTs)
  {
    GNUNET_break_op (0);
    pay_end (pc,
             TALER_MHD_reply_with_error (
               pc->connection,
               MHD_HTTP_BAD_REQUEST,
               TALER_EC_GENERIC_PARAMETER_MALFORMED,
               "'tokens' array too long"));
    return;
  }

  pc->parse_pay.tokens = GNUNET_new_array (pc->parse_pay.tokens_cnt,
                                           struct TokenUseConfirmation);

  /* This look populates the array 'tokens' in 'pc' */
  {
    unsigned int tokens_index;
    json_t *token;

    json_array_foreach (tokens, tokens_index, token)
    {
      struct TokenUseConfirmation *tuc = &pc->parse_pay.tokens[tokens_index];
      struct GNUNET_JSON_Specification ispec[] = {
        GNUNET_JSON_spec_fixed_auto ("token_sig",
                                     &tuc->sig),
        GNUNET_JSON_spec_fixed_auto ("token_pub",
                                     &tuc->pub),
        GNUNET_JSON_spec_fixed_auto ("h_issue",
                                     &tuc->h_issue),
        TALER_JSON_spec_token_issue_sig ("ub_sig",
                                         &tuc->unblinded_sig),
        GNUNET_JSON_spec_end ()
      };
      enum GNUNET_GenericReturnValue res;

      res = TALER_MHD_parse_json_data (pc->connection,
                                       token,
                                       ispec);
      if (GNUNET_YES != res)
      {
        GNUNET_break_op (0);
        pay_end (pc,
                 (GNUNET_NO == res)
                 ? MHD_YES
                 : MHD_NO);
        return;
      }

      for (unsigned int j = 0; j<tokens_index; j++)
      {
        if (0 ==
            GNUNET_memcmp (&tuc->pub,
                           &pc->parse_pay.tokens[j].pub))
        {
          GNUNET_break_op (0);
          pay_end (pc,
                   TALER_MHD_reply_with_error (pc->connection,
                                               MHD_HTTP_BAD_REQUEST,
                                               TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                               "duplicate token in list"));
          return;
        }
      }
    }
  }

  pc->phase = PP_PARSE_WALLET_DATA;
}


/**
 * Custom cleanup routine for a `struct PayContext`.
 *
 * @param cls the `struct PayContext` to clean up.
 */
static void
pay_context_cleanup (void *cls)
{
  struct PayContext *pc = cls;

  if (NULL != pc->batch_deposits.timeout_task)
  {
    GNUNET_SCHEDULER_cancel (pc->batch_deposits.timeout_task);
    pc->batch_deposits.timeout_task = NULL;
  }
  if (NULL != pc->check_contract.contract_terms_json)
  {
    json_decref (pc->check_contract.contract_terms_json);
    pc->check_contract.contract_terms_json = NULL;
  }
  for (unsigned int i = 0; i<pc->parse_pay.coins_cnt; i++)
  {
    struct DepositConfirmation *dc = &pc->parse_pay.dc[i];

    TALER_denom_sig_free (&dc->cdd.denom_sig);
    GNUNET_free (dc->exchange_url);
  }
  GNUNET_free (pc->parse_pay.dc);
  for (unsigned int i = 0; i<pc->parse_pay.tokens_cnt; i++)
  {
    struct TokenUseConfirmation *tuc = &pc->parse_pay.tokens[i];

    TALER_token_issue_sig_free (&tuc->unblinded_sig);
  }
  GNUNET_free (pc->parse_pay.tokens);
  for (unsigned int i = 0; i<pc->parse_pay.num_exchanges; i++)
  {
    struct ExchangeGroup *eg = pc->parse_pay.egs[i];

    if (NULL != eg->fo)
      TMH_EXCHANGES_keys4exchange_cancel (eg->fo);
    GNUNET_free (eg);
  }
  GNUNET_free (pc->parse_pay.egs);
  if (NULL != pc->check_contract.contract_terms)
  {
    TALER_MERCHANT_contract_free (pc->check_contract.contract_terms);
    pc->check_contract.contract_terms = NULL;
  }
  if (NULL != pc->response)
  {
    MHD_destroy_response (pc->response);
    pc->response = NULL;
  }
  GNUNET_free (pc->parse_pay.session_id);
  GNUNET_CONTAINER_DLL_remove (pc_head,
                               pc_tail,
                               pc);
  GNUNET_free (pc->check_contract.pos_key);
#ifdef HAVE_DONAU_DONAU_SERVICE_H
  if (NULL != pc->parse_wallet_data.bkps)
  {
    for (size_t i = 0; i < pc->parse_wallet_data.num_bkps; i++)
      GNUNET_CRYPTO_blinded_message_decref (
        pc->parse_wallet_data.bkps[i].blinded_udi.blinded_message);
    GNUNET_array_grow (pc->parse_wallet_data.bkps,
                       pc->parse_wallet_data.num_bkps,
                       0);
  }
  if (NULL != pc->parse_wallet_data.donau_keys)
  {
    DONAU_keys_decref (pc->parse_wallet_data.donau_keys);
    pc->parse_wallet_data.donau_keys = NULL;
  }
  GNUNET_free (pc->parse_wallet_data.donau.donau_url);
#endif
  for (unsigned int i = 0; i<pc->parse_wallet_data.token_envelopes_cnt; i++)
  {
    struct TokenEnvelope *ev
      = &pc->parse_wallet_data.token_envelopes[i];

    GNUNET_CRYPTO_blinded_message_decref (ev->blinded_token.blinded_pub);
  }
  GNUNET_free (pc->parse_wallet_data.token_envelopes);
  if (NULL != pc->output_tokens)
  {
    for (unsigned int i = 0; i<pc->output_tokens_len; i++)
      if (NULL != pc->output_tokens[i].sig.signature)
        GNUNET_CRYPTO_blinded_sig_decref (pc->output_tokens[i].sig.signature);
    GNUNET_free (pc->output_tokens);
    pc->output_tokens = NULL;
  }
  GNUNET_free (pc);
}


MHD_RESULT
TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
                        struct MHD_Connection *connection,
                        struct TMH_HandlerContext *hc)
{
  struct PayContext *pc = hc->ctx;

  GNUNET_assert (NULL != hc->infix);
  if (NULL == pc)
  {
    pc = GNUNET_new (struct PayContext);
    pc->connection = connection;
    pc->hc = hc;
    pc->order_id = hc->infix;
    hc->ctx = pc;
    hc->cc = &pay_context_cleanup;
    GNUNET_CONTAINER_DLL_insert (pc_head,
                                 pc_tail,
                                 pc);
  }
  while (1)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Processing /pay in phase %d\n",
                (int) pc->phase);
    switch (pc->phase)
    {
    case PP_PARSE_PAY:
      phase_parse_pay (pc);
      break;
    case PP_PARSE_WALLET_DATA:
      phase_parse_wallet_data (pc);
      break;
    case PP_CHECK_CONTRACT:
      phase_check_contract (pc);
      break;
    case PP_VALIDATE_TOKENS:
      phase_validate_tokens (pc);
      break;
    case PP_CONTRACT_PAID:
      phase_contract_paid (pc);
      break;
    case PP_PAY_TRANSACTION:
      phase_execute_pay_transaction (pc);
      break;
    case PP_REQUEST_DONATION_RECEIPT:
#ifdef HAVE_DONAU_DONAU_SERVICE_H
      phase_request_donation_receipt (pc);
#else
      pc->phase++;
#endif
      break;
    case PP_FINAL_OUTPUT_TOKEN_PROCESSING:
      phase_final_output_token_processing (pc);
      break;
    case PP_PAYMENT_NOTIFICATION:
      phase_payment_notification (pc);
      break;
    case PP_SUCCESS_RESPONSE:
      phase_success_response (pc);
      break;
    case PP_BATCH_DEPOSITS:
      phase_batch_deposits (pc);
      break;
    case PP_RETURN_RESPONSE:
      phase_return_response (pc);
      break;
    case PP_FAIL_LEGAL_REASONS:
      phase_fail_for_legal_reasons (pc);
      break;
    case PP_END_YES:
      return MHD_YES;
    case PP_END_NO:
      return MHD_NO;
    default:
      /* should not be reachable */
      GNUNET_assert (0);
      return MHD_NO;
    }
    switch (pc->suspended)
    {
    case GNUNET_SYSERR:
      /* during shutdown, we don't generate any more replies */
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Processing /pay ends due to shutdown in phase %d\n",
                  (int) pc->phase);
      return MHD_NO;
    case GNUNET_NO:
      /* continue to next phase */
      break;
    case GNUNET_YES:
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Processing /pay suspended in phase %d\n",
                  (int) pc->phase);
      return MHD_YES;
    }
  }
  /* impossible to get here */
  GNUNET_assert (0);
  return MHD_YES;
}


/* end of taler-merchant-httpd_post-orders-ID-pay.c */
