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

  TALER is free software; you can redistribute it and/or modify
  it under the terms of the GNU 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 testing_api_cmd_pay_order.c
 * @brief command to test the /orders/ID/pay feature.
 * @author Marcello Stanisci
 * @author Christian Grothoff
 */
#include "platform.h"
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_json_lib.h>
#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
#include <stddef.h>
#include <stdint.h>
#include <taler/taler_exchange_service.h>
#include <taler/taler_testing_lib.h>
#include <taler/taler_signatures.h>
#include "taler_merchant_service.h"
#include "taler_merchant_pay_service.h"
#include "taler_merchant_testing_lib.h"

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

#ifdef HAVE_DONAU_DONAU_SERVICE_H
/**
 * Struct for handling the CS approach in signing of the bkps
 */
struct CSR_Data
{
  /**
   * Handle to the "batch issue receipt status" operation.
   */
  struct DONAU_CsRBatchIssueHandle *csr_handle;

  /**
   * CS-Nonce
   */
  union GNUNET_CRYPTO_BlindSessionNonce nonce;

  /**
   * batch issue receipt status state
   */
  struct StatusState *ss;

  /**
   * array position in batch issue receipt request (first position is zero)
   */
  size_t position;
};

/**
 * Handling all data needed for the /pay DONAU CMD.
 */
struct MerchantDonauPayData
{
  /**
   * Donau URL.
   */
  const char *donau_url;

  /**
   * Donau keys
   */
  struct DONAU_Keys *keys;

  /**
   * Charity reference
   */
  const char *charity_reference;

  /**
   * Charity id
   */
  uint64_t charity_id;

  /**
   * Amount of the donation
   */
  struct TALER_Amount donation_amount;

  /**
   * Number of BUDIs to create or fetch. Example only.
   */
  uint32_t num_bkps;

  /**
   * Year of the donation
   */
  uint64_t year;

  /**
   * Selected donation unit pub keys for this pay order request
   */
  struct DONAU_DonationUnitPublicKey *selected_pks;

  /**
   * BUDI key pairs used in the payment (blinded_udi + pubkey).
   */
  struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps;

  /**
   * Blinding secrets, if needed for each BUDI (CS vs. RSA).
   */
  union GNUNET_CRYPTO_BlindingSecretP *blinding_secrets;

  /**
   * Blinding values. Cs-nonces, cipher.
   */
  const struct DONAU_BatchIssueValues **alg_values;

  /**
   * Hash of the salted donor tax id, if relevant.
   */
  struct DONAU_HashDonorTaxId h_donor_tax_id;

  /**
   * Array of donation receipts;
   */
  struct DONAU_DonationReceipt *receipts;

  /**
   * Array of hashed udis.
   */
  struct DONAU_UniqueDonorIdentifierHashP *h_udis;

  /**
   * If using the CS approach, we might track how many
   * asynchronous calls are still pending, etc.
   */
  unsigned int cs_pending;

  /**
   * Budis Key Pairs json
   */
  json_t *budis_json;
};


/**
 * Prepares the donau data for the /pay CMD.
 *
 * @param is interpreter state
 * @param ss donau data to prepare
 */
static enum GNUNET_GenericReturnValue
prepare_donau_data (struct TALER_TESTING_Interpreter *is,
                    struct MerchantDonauPayData *ss)
{
  /* Get charity id and the charity private key from trait */
  {
    const struct TALER_TESTING_Command *charity_post_cmd;
    const uint64_t *charity_id;

    charity_post_cmd = TALER_TESTING_interpreter_lookup_command (
      is,
      ss->charity_reference);

    if (GNUNET_OK !=
        TALER_TESTING_get_trait_charity_id (charity_post_cmd,
                                            &charity_id))
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
    ss->charity_id = (uint64_t) *(charity_id);
  }

  /* Get donau keys from trait */
  {
    const struct TALER_TESTING_Command *keys_cmd;
    struct DONAU_Keys *keys;

    keys_cmd = TALER_TESTING_interpreter_get_command (is,
                                                      "donau");

    if (GNUNET_OK !=
        TALER_TESTING_get_trait_donau_keys (keys_cmd,
                                            &keys))
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }
    ss->keys = keys;
  }

  /* Get selected_pks + num_bkps*/
  {
    enum GNUNET_GenericReturnValue sret;

    sret = DONAU_select_donation_unit_keys_for_amount (
      ss->keys,
      &ss->donation_amount,
      ss->year,
      &ss->selected_pks,
      &ss->num_bkps);

    if (GNUNET_SYSERR == sret)
    {
      GNUNET_break (0);
      TALER_TESTING_interpreter_fail (is);
      return GNUNET_SYSERR;
    }
    if ((GNUNET_NO == sret) || (0 == ss->num_bkps))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Could not compose exact amount from donation units\n");
      TALER_TESTING_interpreter_fail (is);
      return GNUNET_NO;
    }
  }

  /* Get BUDIsKP */
  {
    ss->bkps
      = GNUNET_new_array (ss->num_bkps,
                          struct DONAU_BlindedUniqueDonorIdentifierKeyPair);
    ss->blinding_secrets
      = GNUNET_new_array (ss->num_bkps,
                          union GNUNET_CRYPTO_BlindingSecretP);
    ss->receipts
      = GNUNET_new_array (ss->num_bkps,
                          struct DONAU_DonationReceipt);
    ss->alg_values
      = GNUNET_new_array (ss->num_bkps,
                          const struct DONAU_BatchIssueValues *);
    ss->h_udis
      = GNUNET_new_array (ss->num_bkps,
                          struct DONAU_UniqueDonorIdentifierHashP);

    for (size_t cnt = 0; cnt < ss->num_bkps; cnt++)
    {
      struct DONAU_UniqueDonorIdentifierNonce *udi_nonce;
      struct DONAU_BudiMasterSecretP ps;
      const struct DONAU_BatchIssueValues *alg_values;
      struct DONAU_BlindedUniqueDonorIdentifier *blinded_udi;
      struct DONAU_UniqueDonorIdentifierHashP *udi_hash;

      DONAU_donation_unit_pub_hash (&ss->selected_pks[cnt],
                                    &ss->bkps[cnt].h_donation_unit_pub);

      ss->receipts[cnt].h_donation_unit_pub
        = ss->bkps[cnt].h_donation_unit_pub;
      udi_nonce
        = &ss->receipts[cnt].nonce;
      blinded_udi
        = &ss->bkps[cnt].blinded_udi;
      udi_hash = &ss->h_udis[cnt];

      GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
                                  &ps,
                                  sizeof (ps));
      GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                                  udi_nonce,
                                  sizeof (*udi_nonce));
      switch (ss->selected_pks[cnt].bsign_pub_key->cipher)
      {
      case GNUNET_CRYPTO_BSA_RSA:
        alg_values = DONAU_donation_unit_ewv_rsa_singleton ();
        DONAU_budi_secret_create (&ps,
                                  alg_values,
                                  &ss->blinding_secrets[cnt]);
        GNUNET_assert (GNUNET_OK ==
                       DONAU_donation_unit_blind (
                         &ss->selected_pks[cnt],
                         &ss->blinding_secrets[cnt],
                         NULL,                    /* no cs-nonce needed for rsa */
                         udi_nonce,
                         &ss->h_donor_tax_id,
                         alg_values,
                         udi_hash,
                         blinded_udi));
        ss->alg_values[cnt] = alg_values;
        break;
      case GNUNET_CRYPTO_BSA_CS:
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "CS donation-unit key not yet supported – skip");
        return GNUNET_NO;
        /* FIXME: BUG-#### Cs support missing/broken for donau
        struct CSR_Data *csr_data = GNUNET_new (struct CSR_Data);
        TALER_cs_withdraw_nonce_derive ( // FIXME: write new method
          (struct TALER_PlanchetMasterSecretP *) &ps,
          &csr_data->nonce.cs_nonce);
        csr_data->ss = is;
        csr_data->position = cnt;

        csr_data->csr_handle = DONAU_csr_issue (
          TALER_TESTING_interpreter_get_context (is),
          TALER_TESTING_get_donau_url (is),
          &ss->selected_pks[cnt],
          &csr_data->nonce.cs_nonce,
          &cs_stage_two_callback,
          csr_data);
        if (NULL == csr_data->csr_handle)
        {
          GNUNET_break (0);
        }
        ss->cs_pending++; */
        break;
      default:
        GNUNET_break (0);
      }
    }

    {
      json_t *budikeypairs = json_array ();

      GNUNET_assert (NULL != budikeypairs);
      for (size_t i = 0; i < ss->num_bkps; i++)
      {
        json_t *budikeypair = GNUNET_JSON_PACK (
          GNUNET_JSON_pack_data_auto ("h_donation_unit_pub",
                                      &ss->bkps[i].h_donation_unit_pub),
          DONAU_JSON_pack_blinded_donation_identifier ("blinded_udi",
                                                       &ss->bkps[i].blinded_udi)
          );

        /* steal the reference into the array */
        GNUNET_assert (0 == json_array_append_new (budikeypairs,
                                                   budikeypair));
      }
      ss->budis_json = budikeypairs;
    }
  }
  return GNUNET_OK;
};
#endif /* HAVE_DONAU_DONAU_SERVICE_H */

/**
 * State for a /pay CMD.
 */
struct PayState
{
  /**
   * Contract terms hash code.
   */
  struct TALER_PrivateContractHashP h_contract_terms;

  /**
   * The interpreter state.
   */
  struct TALER_TESTING_Interpreter *is;

  /**
   * Expected HTTP response status code.
   */
  unsigned int http_status;

  /**
   * Reference to a command that can provide a order id,
   * typically a /proposal test command.
   */
  const char *proposal_reference;

  /**
   * Reference to a command that can provide a coin, so
   * we can pay here.
   */
  const char *coin_reference;

  /**
   * Reference to a command that can provide one or
   * multiple tokens used as inputs for the payment.
   * In the form "LABEL0[/INDEX];LABEL1[/INDEX];..."
   */
  const char *token_reference;

  /**
   * The merchant base URL.
   */
  const char *merchant_url;

  /**
   * Total amount to be paid.
   */
  struct TALER_Amount total_amount;

  /**
   * Amount to be paid, plus the deposit fee.
   */
  const char *amount_with_fee;

  /**
   * Amount to be paid, including NO fees.
   */
  const char *amount_without_fee;

  /**
   * Handle to the pay operation.
   */
  struct TALER_MERCHANT_OrderPayHandle *oph;

  /**
   * Signature from the merchant, set on success.
   */
  struct TALER_MerchantSignatureP merchant_sig;

  /**
   * Array of issued tokens, set on success.
   */
  struct TALER_MERCHANT_PrivateTokenDetails *issued_tokens;

  /**
   * Number of tokens in @e issued_tokens.
   */
  unsigned int num_issued_tokens;

  /**
   * Number of donau_tokens in @e issued_tokens.
   */
  unsigned int num_donau_tokens;

  /**
   * The session for which the payment is made.
   */
  const char *session_id;

  /**
   * base64-encoded key
   */
  const char *pos_key;

  /**
   * Option that add amount of the order
   */
  enum TALER_MerchantConfirmationAlgorithm pos_alg;

  /**
   * Index of the choice to be used in the payment. -1 for orders without choices.
   */
  int choice_index;

#ifdef HAVE_DONAU_DONAU_SERVICE_H
  /**
   * Donau data, if required.
   */
  struct MerchantDonauPayData donau_data;
#endif /* HAVE_DONAU_DONAU_SERVICE_H */
};


/**
 * Find the token issue public key for a given token family @a slug and
 * @a valid_after timestamp.
 *
 * @param token_families json object of token families where the key is the slug
 * @param slug the slug of the token family
 * @param key_index index of the key within the token family
 * @param[out] pub the token issue public key of the token family
 * @return #GNUNET_OK on success and #GNUNET_SYSERR if not found
 */
static enum GNUNET_GenericReturnValue
find_token_public_key (const json_t *token_families,
                       const char *slug,
                       unsigned int key_index,
                       struct TALER_TokenIssuePublicKey *pub)

{
  const json_t *tf = json_object_get (token_families, slug);
  const json_t *keys;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_array_const ("keys",
                                  &keys),
    GNUNET_JSON_spec_end ()
  };
  const json_t *key;
  const char *error_name;
  unsigned int error_line;
  struct GNUNET_JSON_Specification ispec[] = {
    TALER_JSON_spec_token_pub (NULL,
                               pub),
    GNUNET_JSON_spec_end ()
  };

  if (NULL == tf)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Token family `%s' not found\n",
                slug);
    return GNUNET_SYSERR;
  }
  if (GNUNET_OK !=
      GNUNET_JSON_parse (tf,
                         spec,
                         NULL,
                         NULL))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to parse token family `%s'\n",
                slug);
    return GNUNET_SYSERR;
  }

  key = json_array_get (keys,
                        key_index);
  if (NULL == key)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Key with index %u for token family '%s' not found\n",
                key_index,
                slug);
    return GNUNET_SYSERR;
  }
  if (GNUNET_OK !=
      GNUNET_JSON_parse (key,
                         ispec,
                         &error_name,
                         &error_line))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Failed to parse %s at %u: %s\n",
                ispec[error_line].field,
                error_line,
                error_name);
    return GNUNET_SYSERR;
  }
  return GNUNET_OK;
}


/**
 * Parse the @a coins specification and grow the @a pc
 * array with the coins found, updating @a npc.
 *
 * @param[in,out] pc pointer to array of coins found
 * @param[in,out] npc length of array at @a pc
 * @param[in] coins string specifying coins to add to @a pc,
 *            clobbered in the process
 * @param is interpreter state
 * @param amount_with_fee total amount to be paid for a contract.
 * @param amount_without_fee to be removed, there is no
 *        per-contract fee, only per-coin exists.
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
build_coins (struct TALER_MERCHANT_PayCoin **pc,
             unsigned int *npc,
             char *coins,
             struct TALER_TESTING_Interpreter *is,
             const char *amount_with_fee,
             const char *amount_without_fee)
{
  char *token;
  struct TALER_EXCHANGE_Keys *keys;

  keys = TALER_TESTING_get_keys (is);
  if (NULL == keys)
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }

  for (token = strtok (coins, ";");
       NULL != token;
       token = strtok (NULL, ";"))
  {
    const struct TALER_TESTING_Command *coin_cmd;
    char *ctok;
    unsigned int ci;
    struct TALER_MERCHANT_PayCoin *icoin;
    const struct TALER_EXCHANGE_DenomPublicKey *dpk;
    const char *exchange_url;

    /* Token syntax is "LABEL[/NUMBER]" */
    ctok = strchr (token, '/');
    /* FIXME: Check why ci variable is parsed but not used? */
    ci = 0;
    if (NULL != ctok)
    {
      *ctok = '\0';
      ctok++;
      if (1 != sscanf (ctok,
                       "%u",
                       &ci))
      {
        GNUNET_break (0);
        return GNUNET_SYSERR;
      }
    }

    coin_cmd = TALER_TESTING_interpreter_lookup_command
                 (is, token);

    if (NULL == coin_cmd)
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }

    GNUNET_array_grow (*pc,
                       *npc,
                       (*npc) + 1);

    icoin = &((*pc)[(*npc) - 1]);

    {
      const struct TALER_CoinSpendPrivateKeyP *coin_priv;
      const struct TALER_DenominationSignature *denom_sig;
      const struct TALER_Amount *denom_value;
      const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
      const struct TALER_AgeCommitmentHashP *h_age_commitment;

      GNUNET_assert (GNUNET_OK ==
                     TALER_TESTING_get_trait_coin_priv (coin_cmd,
                                                        0,
                                                        &coin_priv));
      GNUNET_assert (GNUNET_OK ==
                     TALER_TESTING_get_trait_denom_pub (coin_cmd,
                                                        0,
                                                        &denom_pub));
      GNUNET_assert (GNUNET_OK ==
                     TALER_TESTING_get_trait_denom_sig (coin_cmd,
                                                        0,
                                                        &denom_sig));
      GNUNET_assert (GNUNET_OK ==
                     TALER_TESTING_get_trait_amount (coin_cmd,
                                                     &denom_value));
      GNUNET_assert (GNUNET_OK ==
                     TALER_TESTING_get_trait_h_age_commitment (coin_cmd,
                                                               0,
                                                               &h_age_commitment
                                                               ));
      icoin->coin_priv = *coin_priv;
      icoin->denom_pub = denom_pub->key;
      icoin->denom_sig = *denom_sig;
      icoin->denom_value = *denom_value;
      icoin->amount_with_fee = *denom_value;
      icoin->h_age_commitment = h_age_commitment;
    }
    GNUNET_assert (NULL != (dpk =
                              TALER_TESTING_find_pk (keys,
                                                     &icoin->denom_value,
                                                     false)));

    GNUNET_assert (0 <=
                   TALER_amount_subtract (&icoin->amount_without_fee,
                                          &icoin->denom_value,
                                          &dpk->fees.deposit));
    GNUNET_assert (GNUNET_OK ==
                   TALER_TESTING_get_trait_exchange_url (coin_cmd,
                                                         &exchange_url));
    icoin->exchange_url = exchange_url;
  }

  return GNUNET_OK;
}


/**
 * Parse the @a pay_references specification and grow the @a tokens
 * array with the tokens found, updating @a tokens_num.
 *
 * @param[in,out] tokens array of tokens found
 * @param[in,out] tokens_num length of @a tokens array
 * @param[in] pay_references string of ; separated references to pay commands
              that issued the tokens.
 * @param is interpreter state
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
build_tokens (struct TALER_MERCHANT_UseToken **tokens,
              unsigned int *tokens_num,
              char *pay_references,
              struct TALER_TESTING_Interpreter *is)
{
  char *ref;

  for (ref = strtok (pay_references, ";");
       NULL != ref;
       ref = strtok (NULL, ";"))
  {
    const struct TALER_TESTING_Command *pay_cmd;
    char *slash;
    unsigned int index;
    struct TALER_MERCHANT_UseToken *token;

    /* Reference syntax is "LABEL[/NUMBER]" */
    slash = strchr (ref, '/');
    index = 0;
    if (NULL != slash)
    {
      *slash = '\0';
      slash++;
      if (1 != sscanf (slash,
                       "%u",
                       &index))
      {
        GNUNET_break (0);
        return GNUNET_SYSERR;
      }
    }

    pay_cmd = TALER_TESTING_interpreter_lookup_command (is, ref);

    if (NULL == pay_cmd)
    {
      GNUNET_break (0);
      return GNUNET_SYSERR;
    }

    GNUNET_array_grow (*tokens,
                       *tokens_num,
                       (*tokens_num) + 1);

    token = &((*tokens)[(*tokens_num) - 1]);

    {
      const struct TALER_TokenUsePrivateKeyP *token_priv;
      const struct TALER_TokenIssueSignature *issue_sig;
      const struct TALER_TokenIssuePublicKey *issue_pub;

      GNUNET_assert (GNUNET_OK ==
                     TALER_TESTING_get_trait_token_priv (pay_cmd,
                                                         index,
                                                         &token_priv));

      GNUNET_assert (GNUNET_OK ==
                     TALER_TESTING_get_trait_token_issue_sig (pay_cmd,
                                                              index,
                                                              &issue_sig));

      GNUNET_assert (GNUNET_OK ==
                     TALER_TESTING_get_trait_token_issue_pub (pay_cmd,
                                                              index,
                                                              &issue_pub));

      token->token_priv = *token_priv;
      token->ub_sig = *issue_sig;
      token->issue_pub = *issue_pub;
    }
  }

  return GNUNET_OK;
}


/**
 * Function called with the result of a /pay operation.
 * Checks whether the merchant signature is valid and the
 * HTTP response code matches our expectation.
 *
 * @param cls closure with the interpreter state
 * @param pr HTTP response
 */
static void
pay_cb (void *cls,
        const struct TALER_MERCHANT_PayResponse *pr)
{
  struct PayState *ps = cls;

  ps->oph = NULL;
  if (ps->http_status != pr->hr.http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u (%d) to command (%s) %s\n",
                pr->hr.http_status,
                (int) pr->hr.ec,
                pr->hr.hint,
                TALER_TESTING_interpreter_get_current_label (ps->is));
    TALER_TESTING_FAIL (ps->is);
  }
  if (MHD_HTTP_OK == pr->hr.http_status)
  {
    ps->merchant_sig = pr->details.ok.merchant_sig;
    if (ps->num_issued_tokens + ps->num_donau_tokens !=
        pr->details.ok.num_tokens)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Unexpected number of tokens issued. "
                  "Sent %d envelopes but got %d tokens issued.\n",
                  ps->num_issued_tokens,
                  pr->details.ok.num_tokens);
      GNUNET_break (0);
      TALER_TESTING_interpreter_fail (ps->is);
      return;
    }
    for (unsigned int i = 0; i < ps->num_issued_tokens; i++)
    {
      struct TALER_MERCHANT_PrivateTokenDetails *details =
        &ps->issued_tokens[i];

      /* The issued tokens should be in the
         same order as the provided envelopes. */
      ps->issued_tokens[i].blinded_sig = pr->details.ok.tokens[i].blinded_sig;

      if (GNUNET_OK !=
          TALER_token_issue_sig_unblind (&details->issue_sig,
                                         &details->blinded_sig,
                                         &details->blinding_secret,
                                         &details->h_token_pub,
                                         &details->blinding_inputs,
                                         &details->issue_pub))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Failed to unblind token signature\n");
        GNUNET_break (0);
        TALER_TESTING_interpreter_fail (ps->is);
        return;
      }
    }
    if (NULL != ps->pos_key)
    {
      char *pc;
      bool found = false;

      if (NULL == pr->details.ok.pos_confirmation)
      {
        GNUNET_break (0);
        TALER_TESTING_interpreter_fail (ps->is);
        return;
      }
      pc = TALER_build_pos_confirmation (ps->pos_key,
                                         ps->pos_alg,
                                         &ps->total_amount,
                                         GNUNET_TIME_timestamp_get ());
      /* Check if *any* of our TOTP codes overlaps
         with any of the returned TOTP codes. */
      for (const char *tok = strtok (pc, "\n");
           NULL != tok;
           tok = strtok (NULL, "\n"))
      {
        if (NULL != strstr (pr->details.ok.pos_confirmation,
                            tok))
        {
          found = true;
          break;
        }
      }
      GNUNET_free (pc);
      if (! found)
      {
        GNUNET_break (0);
        TALER_TESTING_interpreter_fail (ps->is);
        return;
      }
    }
  }
  TALER_TESTING_interpreter_next (ps->is);
}


/**
 * Run a "pay" CMD.
 *
 * @param cls closure.
 * @param cmd current CMD being run.
 * @param is interpreter state.
 */
static void
pay_run (void *cls,
         const struct TALER_TESTING_Command *cmd,
         struct TALER_TESTING_Interpreter *is)
{
  struct PayState *ps = cls;
  const struct TALER_TESTING_Command *proposal_cmd;
  const json_t *contract_terms;
  const char *order_id;
  struct GNUNET_TIME_Timestamp refund_deadline;
  struct GNUNET_TIME_Timestamp pay_deadline;
  struct GNUNET_TIME_Timestamp timestamp;
  struct TALER_MerchantPublicKeyP merchant_pub;
  struct TALER_MerchantWireHashP h_wire;
  const struct TALER_PrivateContractHashP *h_proposal;
  struct TALER_Amount max_fee;
  const char *error_name = NULL;
  unsigned int error_line = 0;
  struct TALER_MERCHANT_PayCoin *pay_coins;
  unsigned int npay_coins;
  struct TALER_MERCHANT_UseToken *use_tokens = NULL;
  unsigned int len_use_tokens = 0;
  struct TALER_MERCHANT_OutputToken *output_tokens = NULL;
  unsigned int len_output_tokens = 0;
  const struct TALER_MerchantSignatureP *merchant_sig;
  const enum TALER_MerchantConfirmationAlgorithm *alg_ptr;

  ps->is = is;
  proposal_cmd = TALER_TESTING_interpreter_lookup_command (
    is,
    ps->proposal_reference);

  if (NULL == proposal_cmd)
    TALER_TESTING_FAIL (is);

  if (GNUNET_OK !=
      TALER_TESTING_get_trait_contract_terms (proposal_cmd,
                                              &contract_terms))
    TALER_TESTING_FAIL (is);
  if (NULL == contract_terms)
    TALER_TESTING_FAIL (is);
  if (GNUNET_OK !=
      TALER_TESTING_get_trait_otp_key (proposal_cmd,
                                       &ps->pos_key))
    ps->pos_key = NULL;
  if ( (GNUNET_OK ==
        TALER_TESTING_get_trait_otp_alg (proposal_cmd,
                                         &alg_ptr)) &&
       (NULL != alg_ptr) )
    ps->pos_alg = *alg_ptr;
  {
    /* Get information that needs to be put verbatim in the
     * deposit permission */
    uint64_t version = 0;
    struct GNUNET_JSON_Specification spec[] = {
      GNUNET_JSON_spec_mark_optional (
        GNUNET_JSON_spec_uint64 ("version",
                                 &version),
        NULL),
      GNUNET_JSON_spec_string ("order_id",
                               &order_id),
      GNUNET_JSON_spec_timestamp ("refund_deadline",
                                  &refund_deadline),
      GNUNET_JSON_spec_timestamp ("pay_deadline",
                                  &pay_deadline),
      GNUNET_JSON_spec_timestamp ("timestamp",
                                  &timestamp),
      GNUNET_JSON_spec_fixed_auto ("merchant_pub",
                                   &merchant_pub),
      GNUNET_JSON_spec_fixed_auto ("h_wire",
                                   &h_wire),
      /* FIXME oec: parse minimum age, use data later? */
      GNUNET_JSON_spec_end ()
    };

    if (GNUNET_OK !=
        GNUNET_JSON_parse (contract_terms,
                           spec,
                           &error_name,
                           &error_line))
    {
      char *js;

      js = json_dumps (contract_terms,
                       JSON_INDENT (1));
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Parser failed on %s:%u for input `%s'\n",
                  error_name,
                  error_line,
                  js);
      free (js);
      TALER_TESTING_FAIL (is);
    }
    switch (version)
    {
    case 0:
      {
        struct GNUNET_JSON_Specification v0spec[] = {
          TALER_JSON_spec_amount_any ("amount",
                                      &ps->total_amount),
          TALER_JSON_spec_amount_any ("max_fee",
                                      &max_fee),
          GNUNET_JSON_spec_end ()
        };

        if (GNUNET_OK !=
            GNUNET_JSON_parse (contract_terms,
                               v0spec,
                               &error_name,
                               &error_line))
        {
          char *js;

          js = json_dumps (contract_terms,
                           JSON_INDENT (1));
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      "Parser failed on %s:%u for input `%s'\n",
                      error_name,
                      error_line,
                      js);
          free (js);
          TALER_TESTING_FAIL (is);
        }
      }
      if (0 < ps->choice_index)
        TALER_TESTING_FAIL (is);
      break;
    case 1:
      {
        const json_t *choices;
        const json_t *token_families;
        struct GNUNET_JSON_Specification v1spec[] = {
          GNUNET_JSON_spec_object_const ("token_families",
                                         &token_families),
          GNUNET_JSON_spec_array_const ("choices",
                                        &choices),
          GNUNET_JSON_spec_end ()
        };
        const json_t *outputs;
        json_t *output;
        unsigned int output_index;
        const json_t *choice;

        if (GNUNET_OK !=
            GNUNET_JSON_parse (contract_terms,
                               v1spec,
                               &error_name,
                               &error_line))
        {
          char *js;

          js = json_dumps (contract_terms,
                           JSON_INDENT (1));
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      "Parser failed on %s:%u for input `%s'\n",
                      error_name,
                      error_line,
                      js);
          free (js);
          TALER_TESTING_FAIL (is);
        }

        choice = json_array_get (choices,
                                 ps->choice_index);
        if (NULL == choice)
        {
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      "No choice found at index %d\n",
                      ps->choice_index);
          TALER_TESTING_FAIL (is);
        }

        {
          const char *ierror_name = NULL;
          unsigned int ierror_line = 0;

          struct GNUNET_JSON_Specification ispec[] = {
            TALER_JSON_spec_amount_any ("amount",
                                        &ps->total_amount),
            TALER_JSON_spec_amount_any ("max_fee",
                                        &max_fee),
            GNUNET_JSON_spec_array_const ("outputs",
                                          &outputs),
            GNUNET_JSON_spec_end ()
          };

          if (GNUNET_OK !=
              GNUNET_JSON_parse (choice,
                                 ispec,
                                 &ierror_name,
                                 &ierror_line))
          {
            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                        "Parser failed on %s:%u for input `%s'\n",
                        ierror_name,
                        ierror_line,
                        json_dumps (choice,
                                    JSON_INDENT (2)));
            TALER_TESTING_FAIL (is);
          }
        }

        json_array_foreach (outputs, output_index, output)
        {
          const char *slug;
          const char *kind;
          uint32_t key_index;
          uint32_t count = 1;
          const char *ierror_name = NULL;
          unsigned int ierror_line = 0;

          struct GNUNET_JSON_Specification typespec[] = {
            GNUNET_JSON_spec_string ("type",
                                     &kind),
            GNUNET_JSON_spec_end ()
          };

          if (GNUNET_OK !=
              GNUNET_JSON_parse (output,
                                 typespec,
                                 &ierror_name,
                                 &ierror_line))
          {
            GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                        "Parser failed on %s:%u for input `%s'\n",
                        ierror_name,
                        ierror_line,
                        json_dumps (output,
                                    JSON_INDENT (2)));
            TALER_TESTING_FAIL (is);
          }

          if (0 == strcmp ("tax-receipt",
                           kind))
          {
            const json_t *donau_urls;

            // For test, we care only about the presence of it
            struct GNUNET_JSON_Specification donauspec[] = {
              GNUNET_JSON_spec_array_const ("donau_urls",
                                            &donau_urls),
              GNUNET_JSON_spec_end ()
            };

            if (GNUNET_OK !=
                GNUNET_JSON_parse (output,
                                   donauspec,
                                   &ierror_name,
                                   &ierror_line))
            {
              GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                          "Parser failed on %s:%u for input `%s'\n",
                          ierror_name,
                          ierror_line,
                          json_dumps (output,
                                      JSON_INDENT (2)));
              TALER_TESTING_FAIL (is);
            }

#ifdef HAVE_DONAU_DONAU_SERVICE_H
            {
              const char   *donau_url_str;

              if ( (NULL == donau_urls) ||
                   (0 == json_array_size (donau_urls)) )
              {
                GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                            "No donau_urls found in output\n");
                TALER_TESTING_FAIL (is);
              }

              donau_url_str = json_string_value (json_array_get (donau_urls,
                                                                 0));
              if (NULL == donau_url_str)
              {
                GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                            "First entry in donau_urls is not a string\n");
                TALER_TESTING_FAIL (is);
              }

              ps->donau_data.donau_url = GNUNET_strdup (donau_url_str);

              if (NULL != ps->donau_data.charity_reference)
              {
                switch (prepare_donau_data (is,
                                            &ps->donau_data))
                {
                case GNUNET_OK:
                  break;
                case GNUNET_NO:
                  TALER_TESTING_interpreter_next (ps->is);
                  return;
                case GNUNET_SYSERR:
                  TALER_TESTING_FAIL (is);
                  return;
                }
                ps->num_donau_tokens = ps->donau_data.num_bkps;
              }
            }
#else /* HAVE_DONAU_DONAU_SERVICE_H */
            /* SIMPLY NOTHING */
#endif /* HAVE_DONAU_DONAU_SERVICE_H */
          }

          if (0 == strcmp ("token",
                           kind))
          {
            struct GNUNET_JSON_Specification ispec[] = {
              GNUNET_JSON_spec_string ("token_family_slug",
                                       &slug),
              GNUNET_JSON_spec_uint32 ("key_index",
                                       &key_index),
              GNUNET_JSON_spec_mark_optional (
                GNUNET_JSON_spec_uint32 ("count",
                                         &count),
                NULL),
              GNUNET_JSON_spec_end ()
            };

            if (GNUNET_OK !=
                GNUNET_JSON_parse (output,
                                   ispec,
                                   &ierror_name,
                                   &ierror_line))
            {
              GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                          "Parser failed on %s:%u for input `%s'\n",
                          ierror_name,
                          ierror_line,
                          json_dumps (output,
                                      JSON_INDENT (2)));
              TALER_TESTING_FAIL (is);
            }

            if (0 != strcmp ("token",
                             kind))
            {
              continue;
            }

            GNUNET_array_grow (
              ps->issued_tokens,
              ps->num_issued_tokens,
              ps->num_issued_tokens + count + ps->num_donau_tokens);

            for (unsigned int k = 0; k < count; k++)
            {
              struct TALER_MERCHANT_PrivateTokenDetails *details =
                &ps->issued_tokens[ps->num_issued_tokens - count + k
                                   + ps->num_donau_tokens];

              if (GNUNET_OK !=
                  find_token_public_key (token_families,
                                         slug,
                                         key_index,
                                         &details->issue_pub))
              {
                TALER_TESTING_FAIL (is);
              }

              /* Only RSA is supported for now. */
              GNUNET_assert (GNUNET_CRYPTO_BSA_RSA ==
                             details->issue_pub.public_key->cipher);

              TALER_token_blind_input_copy (&details->blinding_inputs,
                                            TALER_token_blind_input_rsa_singleton ()
                                            );
              /* FIXME: Where to get details->blinding_inputs from? */
              TALER_token_use_setup_random (&details->master);
              TALER_token_use_setup_priv (&details->master,
                                          &details->blinding_inputs,
                                          &details->token_priv);
              TALER_token_use_blinding_secret_create (&details->master,
                                                      &details->blinding_inputs,
                                                      &details->blinding_secret)
              ;
              GNUNET_CRYPTO_eddsa_key_get_public (
                &details->token_priv.private_key,
                &details->token_pub.public_key);
              GNUNET_CRYPTO_hash (&details->token_pub.public_key,
                                  sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
                                  &details->h_token_pub.hash);
              details->envelope.blinded_pub =
                GNUNET_CRYPTO_message_blind_to_sign
                (
                  details->issue_pub.public_key,
                  &details->blinding_secret,
                  NULL,   /* FIXME: Add session nonce to support CS tokens */
                  &details->h_token_pub.hash,
                  sizeof (details->h_token_pub.hash),
                  details->blinding_inputs.blinding_inputs);

              if (NULL == details->envelope.blinded_pub)
              {
                GNUNET_break (0);
                TALER_TESTING_FAIL (is);
              }
            }
          }
        }
      }

      break;
    default:
      TALER_TESTING_FAIL (is);
    }
  }

  {
    char *cr;

    cr = GNUNET_strdup (ps->coin_reference);
    pay_coins = NULL;
    npay_coins = 0;
    if (GNUNET_OK !=
        build_coins (&pay_coins,
                     &npay_coins,
                     cr,
                     is,
                     ps->amount_with_fee,
                     ps->amount_without_fee))
    {
      GNUNET_array_grow (pay_coins,
                         npay_coins,
                         0);
      GNUNET_free (cr);
      TALER_TESTING_FAIL (is);
    }
    GNUNET_free (cr);
  }
  if (NULL != ps->token_reference)
  {
    char *tr;

    tr = GNUNET_strdup (ps->token_reference);
    if (GNUNET_OK !=
        build_tokens (&use_tokens,
                      &len_use_tokens,
                      tr,
                      is))
    {
      GNUNET_array_grow (use_tokens,
                         len_use_tokens,
                         0);
      GNUNET_free (tr);
      TALER_TESTING_FAIL (is);
    }
    GNUNET_free (tr);
  }

  GNUNET_array_grow (output_tokens,
                     len_output_tokens,
                     ps->num_issued_tokens);
  for (unsigned int i = 0; i<len_output_tokens; i++)
  {
    output_tokens[i].envelope.blinded_pub
      = ps->issued_tokens[i].envelope.blinded_pub;
  }

  if (GNUNET_OK !=
      TALER_TESTING_get_trait_merchant_sig (proposal_cmd,
                                            &merchant_sig))
    TALER_TESTING_FAIL (is);

  if (GNUNET_OK !=
      TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
                                                &h_proposal))
    TALER_TESTING_FAIL (is);
  ps->h_contract_terms = *h_proposal;

  /* New logic of setting pay params */
  {
    struct GNUNET_CURL_Context *ctx =
      TALER_TESTING_interpreter_get_context (is);
    struct TALER_MERCHANT_OrderPayOption opts[32];
    size_t oi = 0;

    ps->oph = TALER_MERCHANT_order_pay_create (ctx,
                                               &pay_cb,
                                               ps);

    if (NULL == ps->oph)
      TALER_TESTING_FAIL (is);

#define ADD(_opt)  opts[oi++] = (_opt)
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_MERCHANT_URL (ps->merchant_url));
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_ORDER_ID      (order_id));
    if (NULL != ps->session_id)
      ADD (TALER_MERCHANT_ORDER_PAY_OPTION_SESSION_ID  (ps->session_id));
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_H_CONTRACT    (h_proposal));
    if (ps->choice_index >= 0)
      ADD (TALER_MERCHANT_ORDER_PAY_OPTION_CHOICE_INDEX (ps->choice_index));
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_AMOUNT        (&ps->total_amount));
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_MAX_FEE       (&max_fee));
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_MERCHANT_PUB  (&merchant_pub));
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_TIMESTAMP     (timestamp));
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_REFUND_DEADLINE (refund_deadline));
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_PAY_DEADLINE  (pay_deadline));
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_H_WIRE        (&h_wire));
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_COINS         (npay_coins,
                                                        pay_coins));
    if (len_use_tokens > 0)
      ADD (TALER_MERCHANT_ORDER_PAY_OPTION_INPUT_TOKENS (len_use_tokens,
                                                         use_tokens));
    if (len_output_tokens > 0)
      ADD (TALER_MERCHANT_ORDER_PAY_OPTION_OUTPUT_TOKENS (len_output_tokens,
                                                          output_tokens));

#ifdef HAVE_DONAU_DONAU_SERVICE_H
    if (ps->donau_data.charity_reference)
    {
      ADD (
        TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_URL  (ps->donau_data.donau_url));
      ADD (TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_YEAR (ps->donau_data.year));
      ADD (TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_BUDIS (
             ps->donau_data.budis_json));
    }
#endif
    ADD (TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE ());
#undef ADD

    if (TALER_MERCHANT_OPOEC_OK !=
        TALER_MERCHANT_order_pay_set_options (ps->oph,
                                              opts,
                                              oi))
      TALER_TESTING_FAIL (is);

    if (TALER_MERCHANT_OPOEC_OK !=
        TALER_MERCHANT_order_pay_start (ps->oph))
      TALER_TESTING_FAIL (is);
  }

  GNUNET_array_grow (pay_coins,
                     npay_coins,
                     0);

  GNUNET_array_grow (use_tokens,
                     len_use_tokens,
                     0);

  GNUNET_array_grow (output_tokens,
                     len_output_tokens,
                     0);
}


/**
 * Free a "pay" CMD, and cancel it if need be.
 *
 * @param cls closure.
 * @param cmd command currently being freed.
 */
static void
pay_cleanup (void *cls,
             const struct TALER_TESTING_Command *cmd)
{
  struct PayState *ps = cls;

  if (NULL != ps->oph)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Command `%s' did not complete.\n",
                TALER_TESTING_interpreter_get_current_label (
                  ps->is));
    TALER_MERCHANT_order_pay_cancel1 (ps->oph);
  }

  GNUNET_free (ps);
}


/**
 * Offer internal data useful to other commands.
 *
 * @param cls closure
 * @param[out] ret result
 * @param trait name of the trait
 * @param index index number of the object to extract.
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
pay_traits (void *cls,
            const void **ret,
            const char *trait,
            unsigned int index)
{

  struct PayState *ps = cls;
  const char *order_id;
  const struct TALER_TESTING_Command *proposal_cmd;
  const struct TALER_MerchantPublicKeyP *merchant_pub;

  if (NULL != ps->token_reference &&
      index >= ps->num_issued_tokens)
  {
    GNUNET_break (0);
    return GNUNET_NO;
  }

  if (NULL ==
      (proposal_cmd =
         TALER_TESTING_interpreter_lookup_command (ps->is,
                                                   ps->proposal_reference)))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }

  if (GNUNET_OK !=
      TALER_TESTING_get_trait_order_id (proposal_cmd,
                                        &order_id))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }

  if (GNUNET_OK !=
      TALER_TESTING_get_trait_merchant_pub (proposal_cmd,
                                            &merchant_pub))
  {
    GNUNET_break (0);
    return GNUNET_SYSERR;
  }
  {
    struct TALER_Amount amount_with_fee;

    GNUNET_assert (GNUNET_OK ==
                   TALER_string_to_amount (ps->amount_with_fee,
                                           &amount_with_fee));
    {
      struct TALER_TESTING_Trait traits[] = {
        TALER_TESTING_make_trait_proposal_reference (ps->proposal_reference),
        TALER_TESTING_make_trait_coin_reference (0,
                                                 ps->coin_reference),
        TALER_TESTING_make_trait_order_id (order_id),
        TALER_TESTING_make_trait_merchant_pub (merchant_pub),
        TALER_TESTING_make_trait_merchant_sig (&ps->merchant_sig),
        TALER_TESTING_make_trait_amount (&amount_with_fee),
        TALER_TESTING_make_trait_otp_key (ps->pos_key),
        TALER_TESTING_make_trait_otp_alg (&ps->pos_alg),
        TALER_TESTING_make_trait_token_priv (index,
                                             &ps->issued_tokens[index].
                                             token_priv),
        TALER_TESTING_make_trait_token_issue_pub (index,
                                                  &ps->issued_tokens[index].
                                                  issue_pub),
        TALER_TESTING_make_trait_token_issue_sig (index,
                                                  &ps->issued_tokens[index].
                                                  issue_sig),
        TALER_TESTING_trait_end ()
      };

      return TALER_TESTING_get_trait (traits,
                                      ret,
                                      trait,
                                      index);
    }
  }
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_pay_order_choices (
  const char *label,
  const char *merchant_url,
  unsigned int http_status,
  const char *proposal_reference,
  const char *coin_reference,
  const char *amount_with_fee,
  const char *amount_without_fee,
  const char *session_id,
  int choice_index,
  const char *token_reference)
{
  struct PayState *ps;

  ps = GNUNET_new (struct PayState);
  ps->http_status = http_status;
  ps->proposal_reference = proposal_reference;
  ps->coin_reference = coin_reference;
  ps->merchant_url = merchant_url;
  ps->amount_with_fee = amount_with_fee;
  ps->amount_without_fee = amount_without_fee;
  ps->session_id = session_id;
  ps->token_reference = token_reference;
  ps->choice_index = choice_index;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = ps,
      .label = label,
      .run = &pay_run,
      .cleanup = &pay_cleanup,
      .traits = &pay_traits
    };

    return cmd;
  }
}


#ifdef HAVE_DONAU_DONAU_SERVICE_H

struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_pay_order_donau (
  const char *label,
  const char *merchant_url,
  unsigned int http_status,
  const char *proposal_reference,
  const char *coin_reference,
  const char *amount_with_fee,
  const char *amount_without_fee,
  const char *amount_donation,
  const char *session_id,
  int choice_index,
  const char *charity_reference,
  uint64_t year,
  const char *donor_tax_id,
  const char *salt)
{
  struct PayState *ps;

  ps = GNUNET_new (struct PayState);
  ps->http_status = http_status;
  ps->proposal_reference = proposal_reference;
  ps->coin_reference = coin_reference;
  ps->merchant_url = merchant_url;
  ps->amount_with_fee = amount_with_fee;
  ps->amount_without_fee = amount_without_fee;
  ps->session_id = session_id;
  ps->token_reference = NULL;
  ps->choice_index = choice_index;
  ps->donau_data.year = year;
  ps->donau_data.num_bkps = 5;
  ps->donau_data.charity_reference = charity_reference;
  if (GNUNET_OK !=
      TALER_string_to_amount (amount_donation,
                              &ps->donau_data.donation_amount))
  {
    GNUNET_assert (0);
  }

  /* Compute h_donor_tax_id directly into ps->donau_data: */
  if (! DONAU_compute_salted_tax_id_hash (donor_tax_id,
                                          salt,
                                          ps->donau_data.h_donor_tax_id.hash))
  {
    GNUNET_assert (0);
  }

  {
    struct TALER_TESTING_Command cmd = {
      .cls = ps,
      .label = label,
      .run = &pay_run,
      .cleanup = &pay_cleanup,
      .traits = &pay_traits
    };

    return cmd;
  }
}


#endif /* HAVE_DONAU_DONAU_SERVICE_H */


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_pay_order (
  const char *label,
  const char *merchant_url,
  unsigned int http_status,
  const char *proposal_reference,
  const char *coin_reference,
  const char *amount_with_fee,
  const char *amount_without_fee,
  const char *session_id)
{
  return TALER_TESTING_cmd_merchant_pay_order_choices (
    label,
    merchant_url,
    http_status,
    proposal_reference,
    coin_reference,
    amount_with_fee,
    amount_without_fee,
    session_id,
    -1,
    NULL);
}


/* end of testing_api_cmd_pay_order.c */
