/*
  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_post_orders.c
 * @brief command to run POST /orders
 * @author Marcello Stanisci
 */

#include "platform.h"
#include <gnunet/gnunet_common.h>
#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
#include <stdint.h>
#include "taler_merchant_util.h"
#include <stdlib.h>
#include <math.h>
#include <taler/taler_exchange_service.h>
#include <taler/taler_testing_lib.h>
#include "taler_merchant_service.h"
#include "taler_merchant_testing_lib.h"

/**
 * State for a "POST /orders" CMD.
 */
struct OrdersState
{

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

  /**
   * Order id.
   */
  const char *order_id;

  /**
   * Our configuration.
   */
  const struct GNUNET_CONFIGURATION_Handle *cfg;

  /**
   * The order id we expect the merchant to assign (if not NULL).
   */
  const char *expected_order_id;

  /**
   * Contract terms obtained from the backend.
   */
  json_t *contract_terms;

  /**
   * Order submitted to the backend.
   */
  json_t *order_terms;

  /**
   * Contract terms hash code.
   */
  struct TALER_PrivateContractHashP h_contract_terms;

  /**
   * The /orders operation handle.
   */
  struct TALER_MERCHANT_PostOrdersHandle *po;

  /**
   * The (initial) POST /orders/$ID/claim operation handle.
   * The logic is such that after an order creation,
   * we immediately claim the order.
   */
  struct TALER_MERCHANT_OrderClaimHandle *och;

  /**
   * The nonce.
   */
  struct GNUNET_CRYPTO_EddsaPublicKey nonce;

  /**
   * Whether to generate a claim token.
   */
  bool make_claim_token;

  /**
   * The claim token
   */
  struct TALER_ClaimTokenP claim_token;

  /**
   * URL of the merchant backend.
   */
  const char *merchant_url;

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

  /**
   * Merchant signature over the orders.
   */
  struct TALER_MerchantSignatureP merchant_sig;

  /**
   * Merchant public key.
   */
  struct TALER_MerchantPublicKeyP merchant_pub;

  /**
   * The payment target for the order
   */
  const char *payment_target;

  /**
   * The products the order is purchasing.
   */
  const char *products;

  /**
   * The locks that the order should release.
   */
  const char *locks;

  /**
   * Should the command also CLAIM the order?
   */
  bool with_claim;

  /**
   * If not NULL, the command should duplicate the request and verify the
   * response is the same as in this command.
   */
  const char *duplicate_of;
};


/**
 * Offer internal data to other commands.
 *
 * @param cls closure
 * @param[out] ret result (could be anything)
 * @param trait name of the trait
 * @param index index number of the object to extract.
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
orders_traits (void *cls,
               const void **ret,
               const char *trait,
               unsigned int index)
{
  struct OrdersState *ps = cls;
  struct TALER_TESTING_Trait traits[] = {
    TALER_TESTING_make_trait_order_id (ps->order_id),
    TALER_TESTING_make_trait_contract_terms (ps->contract_terms),
    TALER_TESTING_make_trait_order_terms (ps->order_terms),
    TALER_TESTING_make_trait_h_contract_terms (&ps->h_contract_terms),
    TALER_TESTING_make_trait_merchant_sig (&ps->merchant_sig),
    TALER_TESTING_make_trait_merchant_pub (&ps->merchant_pub),
    TALER_TESTING_make_trait_claim_nonce (&ps->nonce),
    TALER_TESTING_make_trait_claim_token (&ps->claim_token),
    TALER_TESTING_trait_end ()
  };

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


/**
 * Used to fill the "orders" CMD state with backend-provided
 * values.  Also double-checks that the order was correctly
 * created.
 *
 * @param cls closure
 * @param ocr response we got
 */
static void
orders_claim_cb (void *cls,
                 const struct TALER_MERCHANT_OrderClaimResponse *ocr)
{
  struct OrdersState *ps = cls;
  const char *error_name;
  unsigned int error_line;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
                                 &ps->merchant_pub),
    GNUNET_JSON_spec_end ()
  };

  ps->och = NULL;
  if (ps->http_status != ocr->hr.http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Expected status %u, got %u\n",
                ps->http_status,
                ocr->hr.http_status);
    TALER_TESTING_FAIL (ps->is);
  }
  if (MHD_HTTP_OK != ocr->hr.http_status)
  {
    TALER_TESTING_interpreter_next (ps->is);
    return;
  }
  ps->contract_terms = json_deep_copy (
    (json_t *) ocr->details.ok.contract_terms);
  ps->h_contract_terms = ocr->details.ok.h_contract_terms;
  ps->merchant_sig = ocr->details.ok.sig;
  if (GNUNET_OK !=
      GNUNET_JSON_parse (ps->contract_terms,
                         spec,
                         &error_name,
                         &error_line))
  {
    char *log;

    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Parser failed on %s:%u\n",
                error_name,
                error_line);
    log = json_dumps (ps->contract_terms,
                      JSON_INDENT (1));
    fprintf (stderr,
             "%s\n",
             log);
    free (log);
    TALER_TESTING_FAIL (ps->is);
  }
  TALER_TESTING_interpreter_next (ps->is);
}


/**
 * Callback that processes the response following a POST /orders.  NOTE: no
 * contract terms are included here; they need to be taken via the "orders
 * lookup" method.
 *
 * @param cls closure.
 * @param por details about the response
 */
static void
order_cb (void *cls,
          const struct TALER_MERCHANT_PostOrdersReply *por)
{
  struct OrdersState *ps = cls;

  ps->po = NULL;
  if (ps->http_status != por->hr.http_status)
  {
    TALER_TESTING_unexpected_status_with_body (ps->is,
                                               por->hr.http_status,
                                               ps->http_status,
                                               por->hr.reply);
    TALER_TESTING_interpreter_fail (ps->is);
    return;
  }
  switch (por->hr.http_status)
  {
  case 0:
    TALER_LOG_DEBUG ("/orders, expected 0 status code\n");
    TALER_TESTING_interpreter_next (ps->is);
    return;
  case MHD_HTTP_OK:
    if (NULL != por->details.ok.token)
      ps->claim_token = *por->details.ok.token;
    ps->order_id = GNUNET_strdup (por->details.ok.order_id);
    if ((NULL != ps->expected_order_id) &&
        (0 != strcmp (por->details.ok.order_id,
                      ps->expected_order_id)))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Order id assigned does not match\n");
      TALER_TESTING_interpreter_fail (ps->is);
      return;
    }
    if (NULL != ps->duplicate_of)
    {
      const struct TALER_TESTING_Command *order_cmd;
      const struct TALER_ClaimTokenP *prev_token;
      struct TALER_ClaimTokenP zero_token = {0};

      order_cmd = TALER_TESTING_interpreter_lookup_command (
        ps->is,
        ps->duplicate_of);
      if (GNUNET_OK !=
          TALER_TESTING_get_trait_claim_token (order_cmd,
                                               &prev_token))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Could not fetch previous order claim token\n");
        TALER_TESTING_interpreter_fail (ps->is);
        return;
      }
      if (NULL == por->details.ok.token)
        prev_token = &zero_token;
      if (0 != GNUNET_memcmp (prev_token,
                              por->details.ok.token))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                    "Claim tokens for identical requests do not match\n");
        TALER_TESTING_interpreter_fail (ps->is);
        return;
      }
    }
    break;
  case MHD_HTTP_NOT_FOUND:
    TALER_TESTING_interpreter_next (ps->is);
    return;
  case MHD_HTTP_GONE:
    TALER_TESTING_interpreter_next (ps->is);
    return;
  case MHD_HTTP_CONFLICT:
    TALER_TESTING_interpreter_next (ps->is);
    return;
  default:
    {
      char *s = json_dumps (por->hr.reply,
                            JSON_COMPACT);
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Unexpected status code from /orders: %u (%d) at %s; JSON: %s\n",
                  por->hr.http_status,
                  (int) por->hr.ec,
                  TALER_TESTING_interpreter_get_current_label (ps->is),
                  s);
      free (s);
      /**
       * Not failing, as test cases are _supposed_
       * to create non 200 OK situations.
       */
      TALER_TESTING_interpreter_next (ps->is);
    }
    return;
  }

  if (! ps->with_claim)
  {
    TALER_TESTING_interpreter_next (ps->is);
    return;
  }
  if (NULL ==
      (ps->och = TALER_MERCHANT_order_claim (
         TALER_TESTING_interpreter_get_context (ps->is),
         ps->merchant_url,
         ps->order_id,
         &ps->nonce,
         &ps->claim_token,
         &orders_claim_cb,
         ps)))
    TALER_TESTING_FAIL (ps->is);
}


/**
 * Run a "orders" CMD.
 *
 * @param cls closure.
 * @param cmd command currently being run.
 * @param is interpreter state.
 */
static void
orders_run (void *cls,
            const struct TALER_TESTING_Command *cmd,
            struct TALER_TESTING_Interpreter *is)
{
  struct OrdersState *ps = cls;

  ps->is = is;
  if (NULL == json_object_get (ps->order_terms,
                               "order_id"))
  {
    struct GNUNET_TIME_Absolute now;
    char *order_id;

    now = GNUNET_TIME_absolute_get_monotonic (ps->cfg);
    order_id = GNUNET_STRINGS_data_to_string_alloc (
      &now,
      sizeof (now));
    GNUNET_assert (0 ==
                   json_object_set_new (ps->order_terms,
                                        "order_id",
                                        json_string (order_id)));
    GNUNET_free (order_id);
  }
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
                              &ps->nonce,
                              sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
  ps->po = TALER_MERCHANT_orders_post (TALER_TESTING_interpreter_get_context (
                                         is),
                                       ps->merchant_url,
                                       ps->order_terms,
                                       GNUNET_TIME_UNIT_ZERO,
                                       &order_cb,
                                       ps);
  GNUNET_assert (NULL != ps->po);
}


/**
 * Run a "orders" CMD.
 *
 * @param cls closure.
 * @param cmd command currently being run.
 * @param is interpreter state.
 */
static void
orders_run2 (void *cls,
             const struct TALER_TESTING_Command *cmd,
             struct TALER_TESTING_Interpreter *is)
{
  struct OrdersState *ps = cls;
  const json_t *order;
  char *products_string = GNUNET_strdup (ps->products);
  char *locks_string = GNUNET_strdup (ps->locks);
  char *token;
  struct TALER_MERCHANT_InventoryProduct *products = NULL;
  unsigned int products_length = 0;
  const char **locks = NULL;
  unsigned int locks_length = 0;

  ps->is = is;
  if (NULL != ps->duplicate_of)
  {
    const struct TALER_TESTING_Command *order_cmd;
    const json_t *ct;

    order_cmd = TALER_TESTING_interpreter_lookup_command (
      is,
      ps->duplicate_of);
    if (GNUNET_OK !=
        TALER_TESTING_get_trait_order_terms (order_cmd,
                                             &ct))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Could not fetch previous order string\n");
      TALER_TESTING_interpreter_fail (is);
      return;
    }
    order = (json_t *) ct;
  }
  else
  {
    if (NULL == json_object_get (ps->order_terms,
                                 "order_id"))
    {
      struct GNUNET_TIME_Absolute now;
      char *order_id;

      now = GNUNET_TIME_absolute_get_monotonic (ps->cfg);
      order_id = GNUNET_STRINGS_data_to_string_alloc (
        &now.abs_value_us,
        sizeof (now.abs_value_us));
      GNUNET_assert (0 ==
                     json_object_set_new (ps->order_terms,
                                          "order_id",
                                          json_string (order_id)));
      GNUNET_free (order_id);
    }
    order = ps->order_terms;
  }
  if (NULL == order)
  {
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (is);
    return;
  }

  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
                              &ps->nonce,
                              sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
  for (token = strtok (products_string, ";");
       NULL != token;
       token = strtok (NULL, ";"))
  {
    char *ctok;
    struct TALER_MERCHANT_InventoryProduct pd;
    double quantity_double = 0.0;

    /* Token syntax is "[product_id]/[quantity]" */
    ctok = strchr (token, '/');
    if (NULL != ctok)
    {
      *ctok = '\0';
      ctok++;
      {
        char *endptr;

        quantity_double = strtod (ctok,
                                  &endptr);
        if ( (endptr == ctok) || ('\0' != *endptr) ||
             (! isfinite (quantity_double)) || (quantity_double < 0.0))
        {
          GNUNET_break (0);
          break;
        }
      }
    }
    else
    {
      quantity_double = 1.0;
    }
    if (quantity_double <= 0.0)
    {
      GNUNET_break (0);
      break;
    }

    {
      double quantity_floor;
      double frac;
      uint64_t quantity_int;
      uint32_t quantity_frac_local = 0;
      long long scaled;

      quantity_floor = floor (quantity_double);
      frac = quantity_double - quantity_floor;
      quantity_int = (uint64_t) quantity_floor;
      if (frac < 0.0)
      {
        GNUNET_break (0);
        break;
      }
      scaled = llround (frac * (double) MERCHANT_UNIT_FRAC_BASE);
      if (scaled < 0)
      {
        GNUNET_break (0);
        break;
      }
      if (scaled >= (long long) MERCHANT_UNIT_FRAC_BASE)
      {
        quantity_int++;
        scaled -= MERCHANT_UNIT_FRAC_BASE;
      }
      quantity_frac_local = (uint32_t) scaled;
      pd.quantity = quantity_int;
      pd.quantity_frac = quantity_frac_local;
      pd.use_fractional_quantity = (0 != quantity_frac_local);
    }
    pd.product_id = token;

    GNUNET_array_append (products,
                         products_length,
                         pd);
  }
  for (token = strtok (locks_string, ";");
       NULL != token;
       token = strtok (NULL, ";"))
  {
    const struct TALER_TESTING_Command *lock_cmd;
    const char *uuid;

    lock_cmd = TALER_TESTING_interpreter_lookup_command (
      is,
      token);

    if (GNUNET_OK !=
        TALER_TESTING_get_trait_lock_uuid (lock_cmd,
                                           &uuid))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Could not fetch lock uuid\n");
      TALER_TESTING_interpreter_fail (is);
      return;
    }

    GNUNET_array_append (locks,
                         locks_length,
                         uuid);
  }
  ps->po = TALER_MERCHANT_orders_post2 (
    TALER_TESTING_interpreter_get_context (
      is),
    ps->merchant_url,
    order,
    GNUNET_TIME_UNIT_ZERO,
    ps->payment_target,
    products_length,
    products,
    locks_length,
    locks,
    ps->make_claim_token,
    &order_cb,
    ps);
  GNUNET_free (products_string);
  GNUNET_free (locks_string);
  GNUNET_array_grow (products,
                     products_length,
                     0);
  GNUNET_array_grow (locks,
                     locks_length,
                     0);
  GNUNET_assert (NULL != ps->po);
}


/**
 * Run a "orders" CMD.
 *
 * @param cls closure.
 * @param cmd command currently being run.
 * @param is interpreter state.
 */
static void
orders_run3 (void *cls,
             const struct TALER_TESTING_Command *cmd,
             struct TALER_TESTING_Interpreter *is)
{
  struct OrdersState *ps = cls;
  struct GNUNET_TIME_Absolute now;

  ps->is = is;
  now = GNUNET_TIME_absolute_get_monotonic (ps->cfg);
  if (NULL == json_object_get (ps->order_terms,
                               "order_id"))
  {
    char *order_id;

    order_id = GNUNET_STRINGS_data_to_string_alloc (
      &now,
      sizeof (now));
    GNUNET_assert (0 ==
                   json_object_set_new (ps->order_terms,
                                        "order_id",
                                        json_string (order_id)));
    GNUNET_free (order_id);
  }

  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
                              &ps->nonce,
                              sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
  ps->po = TALER_MERCHANT_orders_post (TALER_TESTING_interpreter_get_context (
                                         is),
                                       ps->merchant_url,
                                       ps->order_terms,
                                       GNUNET_TIME_UNIT_ZERO,
                                       &order_cb,
                                       ps);
  GNUNET_assert (NULL != ps->po);
}


/**
 * Free the state of a "orders" CMD, and possibly
 * cancel it if it did not complete.
 *
 * @param cls closure.
 * @param cmd command being freed.
 */
static void
orders_cleanup (void *cls,
                const struct TALER_TESTING_Command *cmd)
{
  struct OrdersState *ps = cls;

  if (NULL != ps->po)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Command '%s' did not complete (orders put)\n",
                cmd->label);
    TALER_MERCHANT_orders_post_cancel (ps->po);
    ps->po = NULL;
  }

  if (NULL != ps->och)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Command '%s' did not complete (orders lookup)\n",
                cmd->label);
    TALER_MERCHANT_order_claim_cancel (ps->och);
    ps->och = NULL;
  }

  json_decref (ps->contract_terms);
  json_decref (ps->order_terms);
  GNUNET_free_nz ((void *) ps->order_id);
  GNUNET_free (ps);
}


/**
 * Mark part of the contract terms as possible to forget.
 *
 * @param cls pointer to the result of the forget operation.
 * @param object_id name of the object to forget.
 * @param parent parent of the object at @e object_id.
 */
static void
mark_forgettable (void *cls,
                  const char *object_id,
                  json_t *parent)
{
  GNUNET_assert (GNUNET_OK ==
                 TALER_JSON_contract_mark_forgettable (parent,
                                                       object_id));
}


/**
 * Constructs the json for a POST order request.
 *
 * @param order_id the name of the order to add, can be NULL.
 * @param refund_deadline the deadline for refunds on this order.
 * @param pay_deadline the deadline for payment on this order.
 * @param amount the amount this order is for, NULL for v1 orders
 * @param[out] order where to write the json string.
 */
static void
make_order_json (const char *order_id,
                 struct GNUNET_TIME_Timestamp refund_deadline,
                 struct GNUNET_TIME_Timestamp pay_deadline,
                 const char *amount,
                 json_t **order)
{
  struct GNUNET_TIME_Timestamp refund = refund_deadline;
  struct GNUNET_TIME_Timestamp pay = pay_deadline;
  json_t *contract_terms;

  /* Include required fields and some dummy objects to test forgetting. */
  contract_terms = json_pack (
    "{s:s, s:s?, s:s?, s:s, s:o, s:o, s:s, s:[{s:s}, {s:s}, {s:s}]}",
    "summary", "merchant-lib testcase",
    "order_id", order_id,
    "amount", amount,
    "fulfillment_url", "https://example.com",
    "refund_deadline", GNUNET_JSON_from_timestamp (refund),
    "pay_deadline", GNUNET_JSON_from_timestamp (pay),
    "dummy_obj", "EUR:1.0",
    "dummy_array", /* For testing forgetting parts of arrays */
    "item", "speakers",
    "item", "headphones",
    "item", "earbuds");
  GNUNET_assert (GNUNET_OK ==
                 TALER_JSON_expand_path (contract_terms,
                                         "$.dummy_obj",
                                         &mark_forgettable,
                                         NULL));
  GNUNET_assert (GNUNET_OK ==
                 TALER_JSON_expand_path (contract_terms,
                                         "$.dummy_array[*].item",
                                         &mark_forgettable,
                                         NULL));
  *order = contract_terms;
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_orders_no_claim (
  const char *label,
  const char *merchant_url,
  unsigned int http_status,
  const char *order_id,
  struct GNUNET_TIME_Timestamp refund_deadline,
  struct GNUNET_TIME_Timestamp pay_deadline,
  const char *amount)
{
  struct OrdersState *ps;

  ps = GNUNET_new (struct OrdersState);
  make_order_json (order_id,
                   refund_deadline,
                   pay_deadline,
                   amount,
                   &ps->order_terms);
  ps->http_status = http_status;
  ps->expected_order_id = order_id;
  ps->merchant_url = merchant_url;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = ps,
      .label = label,
      .run = &orders_run,
      .cleanup = &orders_cleanup,
      .traits = &orders_traits
    };

    return cmd;
  }
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_orders (
  const char *label,
  const struct GNUNET_CONFIGURATION_Handle *cfg,
  const char *merchant_url,
  unsigned int http_status,
  const char *order_id,
  struct GNUNET_TIME_Timestamp refund_deadline,
  struct GNUNET_TIME_Timestamp pay_deadline,
  const char *amount)
{
  struct OrdersState *ps;

  ps = GNUNET_new (struct OrdersState);
  ps->cfg = cfg;
  make_order_json (order_id,
                   refund_deadline,
                   pay_deadline,
                   amount,
                   &ps->order_terms);
  ps->http_status = http_status;
  ps->expected_order_id = order_id;
  ps->merchant_url = merchant_url;
  ps->with_claim = true;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = ps,
      .label = label,
      .run = &orders_run,
      .cleanup = &orders_cleanup,
      .traits = &orders_traits
    };

    return cmd;
  }
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_orders2 (
  const char *label,
  const struct GNUNET_CONFIGURATION_Handle *cfg,
  const char *merchant_url,
  unsigned int http_status,
  const char *order_id,
  struct GNUNET_TIME_Timestamp refund_deadline,
  struct GNUNET_TIME_Timestamp pay_deadline,
  bool claim_token,
  const char *amount,
  const char *payment_target,
  const char *products,
  const char *locks,
  const char *duplicate_of)
{
  struct OrdersState *ps;

  ps = GNUNET_new (struct OrdersState);
  ps->cfg = cfg;
  make_order_json (order_id,
                   refund_deadline,
                   pay_deadline,
                   amount,
                   &ps->order_terms);
  ps->http_status = http_status;
  ps->expected_order_id = order_id;
  ps->merchant_url = merchant_url;
  ps->payment_target = payment_target;
  ps->products = products;
  ps->locks = locks;
  ps->with_claim = (NULL == duplicate_of);
  ps->make_claim_token = claim_token;
  ps->duplicate_of = duplicate_of;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = ps,
      .label = label,
      .run = &orders_run2,
      .cleanup = &orders_cleanup,
      .traits = &orders_traits
    };

    return cmd;
  }
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_orders3 (
  const char *label,
  const struct GNUNET_CONFIGURATION_Handle *cfg,
  const char *merchant_url,
  unsigned int expected_http_status,
  const char *order_id,
  struct GNUNET_TIME_Timestamp refund_deadline,
  struct GNUNET_TIME_Timestamp pay_deadline,
  const char *fulfillment_url,
  const char *amount)
{
  struct OrdersState *ps;

  ps = GNUNET_new (struct OrdersState);
  ps->cfg = cfg;
  make_order_json (order_id,
                   refund_deadline,
                   pay_deadline,
                   amount,
                   &ps->order_terms);
  GNUNET_assert (0 ==
                 json_object_set_new (ps->order_terms,
                                      "fulfillment_url",
                                      json_string (fulfillment_url)));
  ps->http_status = expected_http_status;
  ps->merchant_url = merchant_url;
  ps->with_claim = true;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = ps,
      .label = label,
      .run = &orders_run,
      .cleanup = &orders_cleanup,
      .traits = &orders_traits
    };

    return cmd;
  }
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_orders_choices (
  const char *label,
  const struct GNUNET_CONFIGURATION_Handle *cfg,
  const char *merchant_url,
  unsigned int http_status,
  const char *token_family_slug,
  const char *choice_description,
  json_t *choice_description_i18n,
  unsigned int num_inputs,
  unsigned int num_outputs,
  const char *order_id,
  struct GNUNET_TIME_Timestamp refund_deadline,
  struct GNUNET_TIME_Timestamp pay_deadline,
  const char *amount)
{
  struct OrdersState *ps;
  struct TALER_Amount brutto;
  json_t *choice;
  json_t *choices;
  json_t *inputs;
  json_t *outputs;

  ps = GNUNET_new (struct OrdersState);
  ps->cfg = cfg;
  make_order_json (order_id,
                   refund_deadline,
                   pay_deadline,
                   NULL,
                   &ps->order_terms);
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount (amount,
                                         &brutto));
  inputs = json_array ();
  GNUNET_assert (NULL != inputs);
  GNUNET_assert (0 ==
                 json_array_append_new (
                   inputs,
                   GNUNET_JSON_PACK (
                     GNUNET_JSON_pack_string ("type",
                                              "token"),
                     GNUNET_JSON_pack_uint64 ("count",
                                              num_inputs),
                     GNUNET_JSON_pack_string ("token_family_slug",
                                              token_family_slug)
                     )));
  outputs = json_array ();
  GNUNET_assert (NULL != outputs);
  GNUNET_assert (0 ==
                 json_array_append_new (
                   outputs,
                   GNUNET_JSON_PACK (
                     GNUNET_JSON_pack_string ("type",
                                              "token"),
                     GNUNET_JSON_pack_uint64 ("count",
                                              num_outputs),
                     GNUNET_JSON_pack_string ("token_family_slug",
                                              token_family_slug)
                     )));
  choice
    = GNUNET_JSON_PACK (
        TALER_JSON_pack_amount ("amount",
                                &brutto),
        GNUNET_JSON_pack_allow_null (
          GNUNET_JSON_pack_string ("description",
                                   choice_description)),
        GNUNET_JSON_pack_allow_null (
          GNUNET_JSON_pack_object_steal ("description_i18n",
                                         choice_description_i18n)),
        GNUNET_JSON_pack_array_steal ("inputs",
                                      inputs),
        GNUNET_JSON_pack_array_steal ("outputs",
                                      outputs));
  choices = json_array ();
  GNUNET_assert (NULL != choices);
  GNUNET_assert (0 ==
                 json_array_append_new (
                   choices,
                   choice));
  GNUNET_assert (0 ==
                 json_object_set_new (ps->order_terms,
                                      "choices",
                                      choices)
                 );
  GNUNET_assert (0 ==
                 json_object_set_new (ps->order_terms,
                                      "version",
                                      json_integer (1))
                 );


  ps->http_status = http_status;
  ps->expected_order_id = order_id;
  ps->merchant_url = merchant_url;
  ps->with_claim = true;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = ps,
      .label = label,
      .run = &orders_run3,
      .cleanup = &orders_cleanup,
      .traits = &orders_traits
    };

    return cmd;
  }
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_orders_donau (
  const char *label,
  const struct GNUNET_CONFIGURATION_Handle *cfg,
  const char *merchant_url,
  unsigned int http_status,
  const char *order_id,
  struct GNUNET_TIME_Timestamp refund_deadline,
  struct GNUNET_TIME_Timestamp pay_deadline,
  const char *amount)
{
  struct OrdersState *ps;
  struct TALER_Amount brutto;
  json_t *choice;
  json_t *choices;
  json_t *outputs;

  ps = GNUNET_new (struct OrdersState);
  ps->cfg = cfg;
  make_order_json (order_id,
                   refund_deadline,
                   pay_deadline,
                   NULL,
                   &ps->order_terms);
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount (amount,
                                         &brutto));

  outputs = json_array ();
  GNUNET_assert (NULL != outputs);
  GNUNET_assert (0 ==
                 json_array_append_new (
                   outputs,
                   GNUNET_JSON_PACK (
                     GNUNET_JSON_pack_string ("type",
                                              "tax-receipt")
                     )));
  choice
    = GNUNET_JSON_PACK (
        TALER_JSON_pack_amount ("amount",
                                &brutto),
        GNUNET_JSON_pack_array_steal ("outputs",
                                      outputs));
  choices = json_array ();
  GNUNET_assert (NULL != choices);
  GNUNET_assert (0 ==
                 json_array_append_new (
                   choices,
                   choice));
  GNUNET_assert (0 ==
                 json_object_set_new (ps->order_terms,
                                      "choices",
                                      choices)
                 );
  GNUNET_assert (0 ==
                 json_object_set_new (ps->order_terms,
                                      "version",
                                      json_integer (1))
                 );


  ps->http_status = http_status;
  ps->expected_order_id = order_id;
  ps->merchant_url = merchant_url;
  ps->with_claim = true;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = ps,
      .label = label,
      .run = &orders_run3,
      .cleanup = &orders_cleanup,
      .traits = &orders_traits
    };

    return cmd;
  }
}
