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


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

/**
 * Maximum number of coins that we allow per transaction
 */
#define MAX_COIN_ALLOWED_COINS 1024

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

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

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

  /**
   * 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_AgeAttestation 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
{

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

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

  } 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;

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

  } 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;

};


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

  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 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;
  if (GNUNET_OK !=
      TMH_exchange_check_debit (
        pc->hc->instance->settings.id,
        exchange,
        pc->check_contract.wm,
        &max_amount))
  {
    if (eg->tried_force_keys)
    {
      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;
      }
      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);
        GNUNET_free (dc->age_commitment.keys);
        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);
      GNUNET_free (dc->age_commitment.keys);
    }
    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;
  }

  {
    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;
      GNUNET_assert (off < group_size);
      cdds[off++] = dc->cdd;
    }
    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) as wire fees are unknown\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 = json_array ();

  GNUNET_assert (NULL != token_sigs);
  for (unsigned int i = 0; i < pc->validate_tokens.output_tokens_len; i++)
  {
    GNUNET_assert (0 ==
                   json_array_append_new (
                     token_sigs,
                     GNUNET_JSON_PACK (
                       GNUNET_JSON_pack_blinded_sig (
                         "blind_sig",
                         pc->validate_tokens.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;
  json_t *token_sigs;

  /* 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
                                    );
  token_sigs = (0 >= pc->validate_tokens.output_tokens_len)
    ? NULL
    : build_token_sigs (pc);
  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",
                                             token_sigs)),
             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;
}


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

    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_STORE_FAILED,
                                           "insert used token"));
      return;
    }
    else if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    {
      /* 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;
    }
  }

  {
    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);
    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;
    }
  }

  /* Store signed output tokens in database. */
  for (size_t i = 0; i<pc->validate_tokens.output_tokens_len; i++)
  {
    struct SignedOutputToken *output = &pc->validate_tokens.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);

    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_STORE_FAILED,
                                           "insert output token"));
      return;
    }
  }

  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 = PP_PAYMENT_NOTIFICATION;
}


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

  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 (GNUNET_TIME_timestamp_cmp (ki->valid_after, >, now) ||
          GNUNET_TIME_timestamp_cmp (ki->valid_before, <, now))
      {
        continue; /* ki currently not valid */
      }
      if (0 ==
          GNUNET_memcmp (&ki->pub.public_key->pub_key_hash,
                         &tuc->h_issue.hash))
      {
        key = ki;
        break;
      }
    }
    if (NULL == key)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Input token supplied for public key that is not acceptable\n");
      GNUNET_break (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
 */
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] 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,
                      struct TALER_MERCHANT_ContractTokenFamilyKey *key,
                      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->validate_tokens.output_tokens[pos];

    if ( (pos >= pc->parse_wallet_data.token_envelopes_cnt) ||
         (pos >= pc->validate_tokens.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 (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;
}


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

      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;

        if (input->type != TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN)
        {
          /* only validate inputs of type token (for now) */
          continue;
        }

        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;
        }
      }

      GNUNET_array_grow (pc->validate_tokens.output_tokens,
                         pc->validate_tokens.output_tokens_len,
                         selected->outputs_len);

      for (unsigned int i = 0; i<selected->outputs_len; i++)
      {
        enum GNUNET_DB_QueryStatus qs;
        struct TALER_MERCHANTDB_TokenFamilyKeyDetails details;
        const struct TALER_MERCHANT_ContractOutput *output
          = &selected->outputs[i];
        const struct TALER_MERCHANT_ContractTokenFamily *family;
        struct TALER_MERCHANT_ContractTokenFamilyKey *key;

        if (output->type != TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN)
        {
          /* only validate outputs of type tokens (for now) */
          continue;
        }

        family = find_family (pc,
                              output->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 (output->details.token.key_index >= family->keys_len)
        {
          /* 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,
                     "key index invalid for token family"));
          return;
        }
        key = &family->keys[output->details.token.key_index];
        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);
        if (qs <= 0)
        {
          GNUNET_log (
            GNUNET_ERROR_TYPE_ERROR,
            "Did not find key for %s 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_INTERNAL_SERVER_ERROR,
                     TALER_EC_GENERIC_DB_FETCH_FAILED,
                     NULL));
          return;
        }

        GNUNET_assert (NULL != details.priv.private_key);
        if (GNUNET_OK !=
            sign_token_envelopes (
              pc,
              key,
              &details.priv,
              test_tfk_mandatory (details.token_family.kind),
              i,
              output->details.token.count))
        {
          /* Error is already scheduled from sign_token_envelopes. */
          return;
        }
      }
    }
  }

  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);
      fprintf (stderr,
               "HERE (%u): %s != %s\n",
               (unsigned int) pc->check_contract.contract_terms->version,
               dc->cdd.amount.currency,
               TALER_amount2s (&pc->validate_tokens.brutto));
      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;
    }
  }
}


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;
    }
  }
}


/**
 * 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;
  }
  if (! unmatched)
  {
    /* Everything fine, idempotent request, generate response immediately */
    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;
  }
  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));

  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;
  }

  /* Get details from contract and 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 %ld\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;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_int64 ("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_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 ("token_ev",
                                        &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;
        }
      }
    }
  }

  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 ()
  };

  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.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);
  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_CONTRACT_PAID:
      phase_contract_paid (pc);
      break;
    case PP_VALIDATE_TOKENS:
      phase_validate_tokens (pc);
      break;
    case PP_PAY_TRANSACTION:
      phase_execute_pay_transaction (pc);
      break;
    case PP_BATCH_DEPOSITS:
      phase_batch_deposits (pc);
      break;
    case PP_PAYMENT_NOTIFICATION:
      phase_payment_notification (pc);
      break;
    case PP_SUCCESS_RESPONSE:
      phase_success_response (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;
    }
    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 */
