gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[GNUnet-SVN] [taler-merchant] branch master updated: Implementing new st


From: gnunet
Subject: [GNUnet-SVN] [taler-merchant] branch master updated: Implementing new style test instructions.
Date: Wed, 21 Feb 2018 17:13:43 +0100

This is an automated email from the git hooks/post-receive script.

marcello pushed a commit to branch master
in repository merchant.

The following commit(s) were added to refs/heads/master by this push:
     new 481eda4  Implementing new style test instructions.
481eda4 is described below

commit 481eda431dca5a450ebcc6949753e8ba85e1f9be
Author: Marcello Stanisci <address@hidden>
AuthorDate: Mon Feb 12 17:34:59 2018 +0100

    Implementing new style test instructions.
---
 .gitignore                               |    1 +
 src/include/taler_merchant_testing_lib.h |  507 ++++++++++
 src/lib/Makefile.am                      |   51 +-
 src/lib/merchant_api_refund.c            |    9 +-
 src/lib/test_merchant_api.c              |    4 +-
 src/lib/test_merchant_api.conf           |   10 +-
 src/lib/test_merchant_api_new.c          |  695 ++++++++++++++
 src/lib/testing_api_cmd_history.c        |  302 ++++++
 src/lib/testing_api_cmd_pay.c            | 1538 ++++++++++++++++++++++++++++++
 src/lib/testing_api_cmd_proposal.c       |  560 +++++++++++
 src/lib/testing_api_cmd_refund.c         |  457 +++++++++
 src/lib/testing_api_cmd_tip.c            |  777 +++++++++++++++
 src/lib/testing_api_cmd_track.c          |  362 +++++++
 src/lib/testing_api_helpers.c            |  185 ++++
 src/lib/testing_api_trait_hash.c         |  117 +++
 src/lib/testing_api_trait_merchant_sig.c |   73 ++
 src/lib/testing_api_trait_planchet.c     |   73 ++
 src/lib/testing_api_trait_refund_entry.c |   77 ++
 src/lib/testing_api_trait_string.c       |  131 +++
 19 files changed, 5919 insertions(+), 10 deletions(-)

diff --git a/.gitignore b/.gitignore
index f2f8d1f..cd4a016 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@ GTAGS
 src/backend/taler-merchant-httpd
 src/merchant-tools/taler-merchant-dbinit
 src/lib/test_merchant_api
+src/lib/test_merchant_api_new
 src/lib/test_merchant_api_home/.local/share/taler/exchange/live-keys/
 src/lib/test_merchant_api_home/.local/share/taler/wirefees/
 taler_merchant_config.h
diff --git a/src/include/taler_merchant_testing_lib.h 
b/src/include/taler_merchant_testing_lib.h
new file mode 100644
index 0000000..53f0469
--- /dev/null
+++ b/src/include/taler_merchant_testing_lib.h
@@ -0,0 +1,507 @@
+/*
+  This file is part of TALER
+  (C) 2018 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 include/taler_testing_lib.h
+ * @brief API for writing an interpreter to test Taler components
+ * @author Christian Grothoff <address@hidden>
+ * @author Marcello Stanisci
+ */
+#ifndef TALER_MERCHANT_TESTING_LIB_H
+#define TALER_MERCHANT_TESTING_LIB_H
+
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+
+/* ********************* Helper functions ********************* */
+
+
+#define MERCHANT_FAIL() \
+  do {GNUNET_break (0); return NULL; } while (0)
+
+
+#define CMD_NOT_FOUND "Command not found"
+#define TRAIT_NOT_FOUND "Trait not found"
+
+#define TALER_TESTING_FAIL(is) \
+  do \
+  {\
+    GNUNET_break (0); \
+    TALER_TESTING_interpreter_fail (is); \
+    return; \
+  } while (0)
+
+/**
+ * Prepare the merchant execution.  Create tables and check if
+ * the port is available.
+ *
+ * @param config_filename configuration filename.
+ *
+ * @return the base url, or NULL upon errors.  Must be freed
+ *         by the caller.
+ */
+char *
+TALER_TESTING_prepare_merchant (const char *config_filename);
+
+/**
+ * Start the merchant backend process.  Assume the port
+ * is available and the database is clean.  Use the "prepare
+ * merchant" function to do such tasks.
+ *
+ * @param config_filename configuration filename.
+ *
+ * @return the process, or NULL if the process could not
+ *         be started.
+ */
+struct GNUNET_OS_Process *
+TALER_TESTING_run_merchant (const char *config_filename);
+
+/* ******************* Generic interpreter logic ************ */
+
+/* ************** Specific interpreter commands ************ */
+
+/**
+ * Make the /proposal command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base url.
+ * @param ctx context
+ * @param http_status HTTP status code.
+ * @param order the order
+ * @param instance the merchant instance
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proposal (const char *label,
+                            const char *merchant_url,
+                            struct GNUNET_CURL_Context *ctx,
+                            unsigned int http_status,
+                            const char *order,
+                            const char *instance);
+
+/**
+ * Make a "proposal lookup" command.
+ *
+ * @param label command label
+ * @param http_status expected HTTP response code
+ * @param proposal_reference reference to a proposal command
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proposal_lookup
+  (const char *label,
+   struct GNUNET_CURL_Context *ctx,
+   const char *merchant_url,
+   unsigned int http_status,
+   const char *proposal_reference);
+
+/**
+ * Make a "check payment" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param ctx CURL context.
+ * @param http_status expected HTTP response code.
+ * @param proposal_reference the proposal whose payment status
+ *        is going to be checked.
+ * @param expect_paid GNUNET_YES if we expect the proposal to be
+ *        paid, GNUNET_NO otherwise.
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_payment (const char *label,
+                                 const char *merchant_url,
+                                 struct GNUNET_CURL_Context *ctx,
+                                 unsigned int http_status,
+                                 const char *proposal_reference,
+                                 unsigned int expect_paid);
+
+/**
+ * Make a "pay" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param ctx CURL context.
+ * @param http_status expected HTTP response code.
+ * @param proposal_reference the proposal whose payment status
+ *        is going to be checked.
+ * @param coin_reference reference to any command which is able
+ *        to provide coins to use for paying.
+ * @param amount_with_fee amount to pay, including the deposit
+ *        fee
+ * @param amount_without_fee amount to pay, no fees included.
+ * @param refund_fee fee for refunding this payment.
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay (const char *label,
+                       const char *merchant_url,
+                       struct GNUNET_CURL_Context *ctx,
+                       unsigned int http_status,
+                       const char *proposal_reference,
+                       const char *coin_reference,
+                       const char *amount_with_fee,
+                       const char *amount_without_fee,
+                       const char *refund_fee);
+
+/**
+ * Make a "pay again" test command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param pay_reference reference to the payment to replay
+ * @param coin_reference reference to the coins to use
+ * @param ctx main CURL context
+ * @param http_status expected HTTP response code
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay_again (const char *label,
+                             const char *merchant_url,
+                             const char *pay_reference,
+                             const char *coin_reference,
+                             const char *refund_fee,
+                             struct GNUNET_CURL_Context *ctx,
+                             unsigned int http_status);
+/**
+ * Make a "pay abort" test command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param pay_reference reference to the payment to abort
+ * @param ctx main CURL context
+ * @param http_status expected HTTP response code
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay_abort (const char *label,
+                             const char *merchant_url,
+                             const char *pay_reference,
+                             struct GNUNET_CURL_Context *ctx,
+                             unsigned int http_status);
+
+/**
+ * FIXME.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay_abort_refund
+  (const char *label,
+   struct TALER_EXCHANGE_Handle *exchange,
+   const char *abort_reference,
+   unsigned int num_coins,
+   const char *refund_amount,
+   const char *refund_fee,
+   unsigned int http_status);
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_lookup
+  (const char *label,
+   const char *merchant_url,
+   struct GNUNET_CURL_Context *ctx,
+   const char *increase_reference,
+   const char *pay_reference,
+   const char *order_id);
+
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_increase
+  (const char *label,
+   const char *merchant_url,
+   struct GNUNET_CURL_Context *ctx,
+   const char *reason,
+   const char *order_id,
+   const char *refund_amount,
+   const char *refund_fee);
+
+
+/**
+ * Make a "history" command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param ctx main CURL context
+ * @param http_status expected HTTP response code
+ * @param time FIXME
+ * @param nresult how many results are expected
+ * @param start FIXME.
+ * @param nrows how many row we want to receive, at most.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_history (const char *label,
+                           const char *merchant_url,
+                           struct GNUNET_CURL_Context *ctx,
+                           unsigned int http_status,
+                           struct GNUNET_TIME_Absolute time,
+                           unsigned int nresult,
+                           unsigned int start,
+                           unsigned int nrows);
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_track_transaction
+  (const char *label,
+   const char *merchant_url,
+   struct GNUNET_CURL_Context *ctx,
+   unsigned int http_status,
+   const char *transfer_reference,
+   const char *pay_reference,
+   const char *wire_fee);
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_track_transfer
+  (const char *label,
+   const char *merchant_url,
+   struct GNUNET_CURL_Context *ctx,
+   unsigned int http_status,
+   const char *check_bank_reference,
+   const char *pay_reference);
+
+/* ****** Specific traits supported by this component ******* */
+
+/**
+ * Offer a merchant signature over a contract.
+ *
+ * @param index which signature to offer if there are multiple
+ *        on offer
+ * @param merchant_sig set to the offered signature.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_merchant_sig
+  (unsigned int index,
+   const struct TALER_MerchantSignatureP *merchant_sig);
+
+/**
+ * Obtain a merchant signature over a contract from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which signature to pick if @a cmd has multiple
+ *        on offer
+ * @param merchant_sig[out] set to the wanted signature.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_merchant_sig
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   struct TALER_MerchantSignatureP **merchant_sig);
+
+
+/**
+ * Obtain a reference to a proposal command.  Any command that
+ * works with proposals, might need to offer their reference to
+ * it.  Notably, the "pay" command, offers its proposal reference
+ * to the "pay abort" command as the latter needs to reconstruct
+ * the same data needed by the former in order to use the "pay
+ * abort" API.
+ *
+ * @param cmd command to extract trait from
+ * @param index which reference to pick if @a cmd has multiple
+ *        on offer
+ * @param proposal_reference[out] set to the wanted reference.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_proposal_reference
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   const char **proposal_reference);
+
+/**
+ * Offer a proposal reference.
+ *
+ * @param index which reference to offer if there are
+ *        multiple on offer
+ * @param proposal_reference set to the offered reference.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_proposal_reference
+  (unsigned int index,
+   const char *proposal_reference);
+
+/**
+ * Offer a coin reference.
+ *
+ * @param index which reference to offer if there are
+ *        multiple on offer
+ * @param coin_reference set to the offered reference.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_coin_reference
+  (unsigned int index,
+   const char *coin_reference);
+
+/**
+ * Obtain a reference to any command that can provide coins as
+ * traits.
+ *
+ * @param cmd command to extract trait from
+ * @param index which reference to pick if @a cmd has multiple
+ *        on offer
+ * @param coin_reference[out] set to the wanted reference. NOTE:
+ *        a _single_ reference can contain _multiple_ token,
+ *        using semi-colon as separator.  For example, a _single_
+ *        reference can be this: "coin-ref-1", or even this:
+ *        "coin-ref-1;coin-ref-2".  The "pay" command contains
+ *        functions that can parse such format.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_coin_reference
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   const char **coin_reference);
+
+
+/**
+ * Obtain planchet secrets from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which signature to pick if @a cmd has multiple
+ *        on offer
+ * @param planchet_secrets[out] set to the wanted secrets.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_planchet_secrets
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   struct TALER_PlanchetSecretsP **planchet_secrets);
+
+
+/**
+ * Offer planchet secrets.
+ *
+ * @param index which secrets to offer if there are multiple
+ *        on offer
+ * @param planchet_secrets set to the offered secrets.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_planchet_secrets
+  (unsigned int index,
+   const struct TALER_PlanchetSecretsP *planchet_secrets);
+
+/**
+ * Offer tip id.
+ *
+ * @param index which tip id to offer if there are
+ *        multiple on offer
+ * @param planchet_secrets set to the offered secrets.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_tip_id
+  (unsigned int index,
+   const struct GNUNET_HashCode *tip_id);
+
+
+/**
+ * Obtain tip id from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which signature to pick if @a cmd has multiple
+ *        on offer
+ * @param tip_id[out] set to the wanted data.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_tip_id
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   const struct GNUNET_HashCode **tip_id);
+
+/**
+ * Offer contract terms hash code.
+ *
+ * @param index which hash code to offer if there are
+ *        multiple on offer
+ * @param h_contract_terms set to the offered hash code.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_h_contract_terms
+  (unsigned int index,
+   const struct GNUNET_HashCode *h_contract_terms);
+
+/**
+ * Obtain contract terms hash from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which hash code to pick if @a cmd has multiple
+ *        on offer
+ * @param h_contract_terms[out] set to the wanted data.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_h_contract_terms
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   const const struct GNUNET_HashCode **h_contract_terms);
+
+
+/**
+ * Offer refund entry.
+ *
+ * @param index which tip id to offer if there are
+ *        multiple on offer
+ * @param refund_entry set to the offered refund entry.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_refund_entry
+  (unsigned int index,
+   const struct TALER_MERCHANT_RefundEntry *refund_entry);
+
+
+/**
+ * Obtain refund entry from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which signature to pick if @a cmd has multiple
+ *        on offer
+ * @param refund_entry[out] set to the wanted data.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_refund_entry
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   const struct TALER_MERCHANT_RefundEntry **refund_entry);
+
+#endif
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index d953081..93f154b 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -7,12 +7,17 @@ if USE_COVERAGE
 endif
 
 lib_LTLIBRARIES = \
-  libtalermerchant.la
+  libtalermerchant.la \
+  libtalermerchanttesting.la
 
 libtalermerchant_la_LDFLAGS = \
   -version-info 2:0:0 \
   -no-undefined
 
+libtalermerchanttesting_la_LDFLAGS = \
+  -version-info 2:0:0 \
+  -no-undefined
+
 libtalermerchant_la_SOURCES = \
   merchant_api_proposal.c \
   merchant_api_pay.c \
@@ -35,6 +40,31 @@ libtalermerchant_la_LIBADD = \
   -ljansson \
   $(XLIB)
 
+libtalermerchanttesting_la_SOURCES = \
+  testing_api_cmd_proposal.c \
+  testing_api_cmd_pay.c \
+  testing_api_cmd_refund.c \
+  testing_api_cmd_tip.c \
+  testing_api_cmd_track.c \
+  testing_api_cmd_history.c \
+  testing_api_helpers.c \
+  testing_api_trait_merchant_sig.c \
+  testing_api_trait_string.c \
+  testing_api_trait_hash.c \
+  testing_api_trait_planchet.c \
+  testing_api_trait_refund_entry.c
+
+libtalermerchanttesting_la_LIBADD = \
+  -ltalerexchange \
+  -ltalerjson \
+  -ltalerutil \
+  -lgnunetcurl \
+  -lgnunetjson \
+  -lgnunetutil \
+  -ljansson \
+  -ltalertesting \
+  $(XLIB)
+
 if HAVE_LIBCURL
 libtalermerchant_la_LIBADD += -lcurl
 else
@@ -45,12 +75,31 @@ endif
 
 if HAVE_TALERFAKEBANK
 check_PROGRAMS = \
+  test_merchant_api_new \
   test_merchant_api
 endif
 
 TESTS = \
   $(check_PROGRAMS)
 
+test_merchant_api_new_SOURCES = \
+  test_merchant_api_new.c
+test_merchant_api_new_LDADD = \
+  $(top_srcdir)/src/backenddb/libtalermerchantdb.la \
+  libtalermerchant.la \
+  $(LIBGCRYPT_LIBS) \
+  -ltalertesting \
+  -ltalermerchanttesting \
+  -ltalerfakebank \
+  -ltalerbank \
+  -ltalerexchange \
+  -ltalerjson \
+  -ltalerutil \
+  -lgnunetjson \
+  -lgnunetcurl \
+  -lgnunetutil \
+  -ljansson
+
 test_merchant_api_SOURCES = \
   test_merchant_api.c
 test_merchant_api_LDADD = \
diff --git a/src/lib/merchant_api_refund.c b/src/lib/merchant_api_refund.c
index 01fa6bd..2f02735 100644
--- a/src/lib/merchant_api_refund.c
+++ b/src/lib/merchant_api_refund.c
@@ -316,17 +316,20 @@ TALER_MERCHANT_refund_lookup (struct GNUNET_CURL_Context 
*ctx,
 {
   struct TALER_MERCHANT_RefundLookupOperation *rlo;
   CURL *eh;
+  char *base;
 
   rlo = GNUNET_new (struct TALER_MERCHANT_RefundLookupOperation);
   rlo->ctx = ctx;
   rlo->cb = cb;
   rlo->cb_cls = cb_cls;
-
+  
+  base = TALER_url_join (backend_url, "/public/refund", NULL);
   GNUNET_asprintf (&rlo->url,
-                   "%s/public/refund?instance=%s&order_id=%s",
-                   backend_url,
+                   "%s?instance=%s&order_id=%s",
+                   base,
                    instance,
                    order_id);
+  GNUNET_free (base);
   eh = curl_easy_init ();
   if (CURLE_OK != curl_easy_setopt (eh,
                                     CURLOPT_URL,
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
index c8ae9f8..c968168 100644
--- a/src/lib/test_merchant_api.c
+++ b/src/lib/test_merchant_api.c
@@ -40,7 +40,7 @@
 /**
  * URL under which the exchange is reachable during the testcase.
  */
-#define EXCHANGE_URL "http://localhost:8084/";
+#define EXCHANGE_URL "http://localhost:8081/";
 
 /**
  * Account number of the exchange at the bank.
@@ -1299,6 +1299,7 @@ history_cb (void *cls,
         fail (is);
         return;
       }
+    /* entry_timestamp should always become last_timestamp */
     entry_timestamp = GNUNET_TIME_absolute_max (last_timestamp, 
entry_timestamp);
     if (last_timestamp.abs_value_us != entry_timestamp.abs_value_us)
     {
@@ -3822,6 +3823,7 @@ interpreter_run (void *cls)
     const struct Command *proposal_ref;
     const char *order_id;
 
+    /* get proposal reference, and order_id from it */
     GNUNET_assert(NULL != (ref = find_command
       (is,
        cmd->details.track_transaction.pay_ref)));
diff --git a/src/lib/test_merchant_api.conf b/src/lib/test_merchant_api.conf
index 924aaa5..ef819e4 100644
--- a/src/lib/test_merchant_api.conf
+++ b/src/lib/test_merchant_api.conf
@@ -72,7 +72,7 @@ CLOSING-FEE-2026 = EUR:0.01
 
 
 [merchant-exchange-test]
-URL = http://localhost:8084/
+URL = http://localhost:8081/
 MASTER_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG
 
 [merchant-instance-default]
@@ -88,13 +88,13 @@ NAME = The Tor Project
 
 [merchant-instance-tip]
 KEYFILE = reserve_tip.priv
-TIP_EXCHANGE = http://localhost:8084/
+TIP_EXCHANGE = http://localhost:8081/
 TIP_RESERVE_PRIV_FILENAME = reserve_key.priv
 NAME = Test Tipping Merchant
 
 [merchant-instance-dtip]
 KEYFILE = reserve_dtip.priv
-TIP_EXCHANGE = http://localhost:8084/
+TIP_EXCHANGE = http://localhost:8081/
 TIP_RESERVE_PRIV_FILENAME = reserve_dkey.priv
 NAME = Test Tipping Merchant 2
 
@@ -144,13 +144,13 @@ ADDRESS = "Garching"
 DB = postgres
 
 # HTTP port the exchange listens to
-PORT = 8084
+PORT = 8081
 
 # Our public key
 MASTER_PUBLIC_KEY = T1VVFQZZARQ1CMF4BN58EE7SKTW5AV2BS18S87ZEGYS4S29J6DNG
 
 # Base URL of the exchange.
-BASE_URL = http://localhost:8084/
+BASE_URL = "http://localhost:8081/";
 
 [exchangedb-postgres]
 DB_CONN_STR = "postgres:///talercheck"
diff --git a/src/lib/test_merchant_api_new.c b/src/lib/test_merchant_api_new.c
new file mode 100644
index 0000000..8615d6a
--- /dev/null
+++ b/src/lib/test_merchant_api_new.c
@@ -0,0 +1,695 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 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 exchange/test_merchant_api_new.c
+ * @brief testcase to test exchange's HTTP API interface
+ * @author Sree Harsha Totakura <address@hidden>
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_util.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_json_lib.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include <taler/taler_bank_service.h>
+#include <taler/taler_fakebank_lib.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_testing_lib.h"
+
+/**
+ * Configuration file we use.  One (big) configuration is used
+ * for the various components for this test.
+ */
+#define CONFIG_FILE "test_merchant_api.conf"
+
+/**
+ * Exchange base URL.  Could also be taken from config.
+ */
+#define EXCHANGE_URL "http://localhost:8081/";
+
+/**
+ * URL of the fakebank.  Obtained from CONFIG_FILE's
+ * "exchange-wire-test:BANK_URI" option.
+ */
+static char *fakebank_url;
+
+/**
+ * Merchant base URL.
+ */
+static char *merchant_url;
+
+/**
+ * Merchant process.
+ */
+static struct GNUNET_OS_Process *merchantd;
+
+/**
+ * Account number of the exchange at the bank.
+ */
+#define EXCHANGE_ACCOUNT_NO 2
+
+/**
+ * Account number of some user.
+ */
+#define USER_ACCOUNT_NO 62
+
+/**
+ * User name. Never checked by fakebank.
+ */
+#define USER_LOGIN_NAME "user42"
+
+/**
+ * User password. Never checked by fakebank.
+ */
+#define USER_LOGIN_PASS "pass42"
+
+/**
+ * Execute the taler-exchange-wirewatch command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_WIREWATCH(label) \
+   TALER_TESTING_cmd_exec_wirewatch (label, CONFIG_FILE)
+
+/**
+ * Execute the taler-exchange-aggregator command with
+ * our configuration file.
+ *
+ * @param label label to use for the command.
+ */
+#define CMD_EXEC_AGGREGATOR(label) \
+   TALER_TESTING_cmd_exec_aggregator (label, CONFIG_FILE)
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ * @param url exchange_url
+ */
+#define CMD_TRANSFER_TO_EXCHANGE(label,amount) \
+   TALER_TESTING_cmd_fakebank_transfer (label, amount, \
+     fakebank_url, USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO, \
+     USER_LOGIN_NAME, USER_LOGIN_PASS, EXCHANGE_URL)
+
+/**
+ * Run wire transfer of funds from some user's account to the
+ * exchange.
+ *
+ * @param label label to use for the command.
+ * @param amount amount to transfer, i.e. "EUR:1"
+ */
+#define CMD_TRANSFER_TO_EXCHANGE_SUBJECT(label,amount,subject) \
+   TALER_TESTING_cmd_fakebank_transfer_with_subject \
+     (label, amount, fakebank_url, USER_ACCOUNT_NO, \
+      EXCHANGE_ACCOUNT_NO, USER_LOGIN_NAME, USER_LOGIN_PASS, \
+      subject)
+
+/**
+ * Main function that will tell the interpreter what commands to
+ * run.
+ *
+ * @param cls closure
+ */
+static void
+run (void *cls,
+     struct TALER_TESTING_Interpreter *is)
+{
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Merchant serves at `%s'\n",
+              merchant_url);
+
+  struct TALER_TESTING_Command commands[] = {
+
+    /**
+     * Move money to the exchange's bank account.
+     */
+    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1",
+                              "EUR:10.02"),
+    /**
+     * Make a reserve exist, according to the previous
+     * transfer.
+     */
+    CMD_EXEC_WIREWATCH ("wirewatch-1"),
+
+    TALER_TESTING_cmd_check_bank_transfer
+      ("check_bank_transfer-2",
+       "http://localhost:8081/";,
+       "EUR:10.02", USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO),
+
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
+                                       is->exchange,
+                                       "create-reserve-1",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+                                       is->exchange,
+                                       "create-reserve-1",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    /**
+     * Check the reserve is depleted.
+     */
+    TALER_TESTING_cmd_status ("withdraw-status-1",
+                              is->exchange,
+                              "create-reserve-1",
+                              "EUR:0",
+                              MHD_HTTP_OK),
+
+    TALER_TESTING_cmd_proposal
+      ("create-proposal-1",
+       merchant_url,
+       is->ctx,
+       MHD_HTTP_OK,
+       "{\"max_fee\":\
+          {\"currency\":\"EUR\",\
+           \"value\":0,\
+           \"fraction\":50000000},\
+        \"order_id\":\"1\",\
+        \"refund_deadline\":\"\\/Date(0)\\/\",\
+        \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
+        \"amount\":\
+          {\"currency\":\"EUR\",\
+           \"value\":5,\
+           \"fraction\":0},\
+        \"summary\": \"merchant-lib testcase\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:5}\"} ] }",
+        NULL),
+
+    TALER_TESTING_cmd_check_payment ("check-payment-1",
+                                     merchant_url,
+                                     is->ctx,
+                                     MHD_HTTP_OK,
+                                     "create-proposal-1",
+                                     GNUNET_NO),
+
+    TALER_TESTING_cmd_pay ("deposit-simple",
+                           merchant_url,
+                           is->ctx,
+                           MHD_HTTP_OK,
+                           "create-proposal-1",
+                           "withdraw-coin-1",
+                           "EUR:5",
+                           "EUR:4.99",
+                           "EUR:0.01"),
+
+    TALER_TESTING_cmd_check_payment ("check-payment-2",
+                                     merchant_url,
+                                     is->ctx,
+                                     MHD_HTTP_OK,
+                                     "create-proposal-1",
+                                     GNUNET_YES),
+
+    TALER_TESTING_cmd_pay_abort ("pay-abort-2",
+                                 merchant_url,
+                                 "deposit-simple",
+                                 is->ctx,
+                                 MHD_HTTP_FORBIDDEN),
+
+    TALER_TESTING_cmd_pay ("replay-simple",
+                           merchant_url,
+                           is->ctx,
+                           MHD_HTTP_OK,
+                           "create-proposal-1",
+                           "withdraw-coin-1",
+                           "EUR:5",
+                           "EUR:4.99",
+                           "EUR:0.01"),
+
+    TALER_TESTING_cmd_proposal
+      ("create-proposal-2",
+       merchant_url,
+       is->ctx,
+       MHD_HTTP_OK,
+       "{\"max_fee\":\
+          {\"currency\":\"EUR\",\
+           \"value\":0,\
+           \"fraction\":50000000},\
+        \"order_id\":\"2\",\
+        \"refund_deadline\":\"\\/Date(0)\\/\",\
+        \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
+        \"amount\":\
+          {\"currency\":\"EUR\",\
+           \"value\":5,\
+           \"fraction\":0},\
+        \"summary\": \"useful product\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:5}\"} ] }",
+        NULL),
+
+    TALER_TESTING_cmd_pay ("deposit-double-2",
+                           merchant_url,
+                           is->ctx,
+                           MHD_HTTP_FORBIDDEN,
+                           "create-proposal-2",
+                           "withdraw-coin-1",
+                           "EUR:5",
+                           "EUR:4.99",
+                           "EUR:0.01"),
+
+    TALER_TESTING_cmd_history ("history-0",
+                               merchant_url,
+                               is->ctx,
+                               MHD_HTTP_OK,
+      /**
+       * all records to be returned; setting date as 0 lets the
+       * interpreter set it as 'now' + one hour delta, just to
+       * make sure it surpasses the proposal's timestamp.
+       */
+                               GNUNET_TIME_UNIT_ZERO_ABS,
+      /**
+       * We only expect ONE result (create-proposal-1) to be
+       * included in /history response, because create-proposal-3
+       * did NOT go through because of double spending.
+       */
+                               1, // nresult
+                               10, // start
+                               10), // nrows
+
+    TALER_TESTING_cmd_fakebank_transfer ("create-reserve-2",
+                                         "EUR:1",
+                                         fakebank_url,
+                                         63, 2,
+                                         "user63",
+                                         "pass63",
+                                         EXCHANGE_URL),
+
+    TALER_TESTING_cmd_fakebank_transfer_with_ref
+      ("create-reserve-2b",
+       "EUR:4.01",
+       fakebank_url,
+       63, 2,
+       "user63",
+       "pass63",
+       "create-reserve-2",
+       EXCHANGE_URL),
+    CMD_EXEC_WIREWATCH ("wirewatch-2"),
+
+    TALER_TESTING_cmd_check_bank_transfer
+      ("check_bank_transfer-2",
+       "http://localhost:8081/";,
+       "EUR:1", 63, 2),
+
+    TALER_TESTING_cmd_check_bank_transfer
+      ("check_bank_transfer-2",
+       "http://localhost:8081/";,
+       "EUR:4.01", 63, 2),
+
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
+                                       is->exchange,
+                                       "create-reserve-2",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+
+    TALER_TESTING_cmd_proposal_lookup ("fetch-proposal-2",
+                                       is->ctx,
+                                       merchant_url,
+                                       MHD_HTTP_OK,
+                                       "create-proposal-2"),
+
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-1"),
+
+    CMD_EXEC_AGGREGATOR ("run-aggregator"),
+
+    TALER_TESTING_cmd_check_bank_transfer
+      ("check_bank_transfer-498c",
+       "http://localhost:8081/";,
+       "EUR:4.98", 2, 62),
+
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-2"),
+
+    TALER_TESTING_cmd_merchant_track_transaction
+      ("track-transaction-1",
+       merchant_url,
+       is->ctx,
+       MHD_HTTP_OK,
+       "check_bank_transfer-498c",
+       "deposit-simple",
+       "EUR:0.01"),
+
+    TALER_TESTING_cmd_merchant_track_transfer
+      ("track-transfer-1",
+       merchant_url,
+       is->ctx,
+       MHD_HTTP_OK,
+       "check_bank_transfer-498c",
+       "deposit-simple"),
+
+    TALER_TESTING_cmd_merchant_track_transfer
+      ("track-transfer-again",
+       merchant_url,
+       is->ctx,
+       MHD_HTTP_OK,
+       "check_bank_transfer-498c",
+       "deposit-simple"),
+
+    TALER_TESTING_cmd_pay ("deposit-simple-2",
+                           merchant_url,
+                           is->ctx,
+                           MHD_HTTP_OK,
+                           "create-proposal-2",
+                           "withdraw-coin-2",
+                           "EUR:5",
+                           "EUR:4.99",
+                           "EUR:0.01"),
+
+    CMD_EXEC_AGGREGATOR ("run-aggregator-2"),
+
+    TALER_TESTING_cmd_check_bank_transfer
+      ("check_bank_transfer-498c-2",
+       "http://localhost:8081/";,
+       "EUR:4.98",
+       EXCHANGE_ACCOUNT_NO,
+       USER_ACCOUNT_NO),
+
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty"),
+
+    TALER_TESTING_cmd_merchant_track_transfer
+      ("track-transfer-2",
+       merchant_url,
+       is->ctx,
+       MHD_HTTP_OK,
+       "check_bank_transfer-498c-2",
+       "deposit-simple-2"),
+
+    TALER_TESTING_cmd_merchant_track_transfer
+      ("track-transfer-2-again",
+       merchant_url,
+       is->ctx,
+       MHD_HTTP_OK,
+       "check_bank_transfer-498c-2",
+       "deposit-simple-2"),
+
+    TALER_TESTING_cmd_merchant_track_transaction
+      ("track-transaction-2",
+       merchant_url,
+       is->ctx,
+       MHD_HTTP_OK,
+       "check_bank_transfer-498c-2",
+       "deposit-simple-2",
+       "EUR:0.01"),
+
+    TALER_TESTING_cmd_history ("history-1",
+                               merchant_url,
+                               is->ctx,
+                               MHD_HTTP_OK,
+                               GNUNET_TIME_UNIT_ZERO_ABS,
+      /**
+       * Now we expect BOTH contracts (create-proposal-{1,2})
+       * to be included in /history response, because
+       * create-proposal-2 has now been correctly paid.
+       */
+                               2,
+                               10,
+                               10),
+
+    TALER_TESTING_cmd_history
+      ("history-2",
+       merchant_url,
+       is->ctx,
+       MHD_HTTP_OK,
+       GNUNET_TIME_absolute_add (GNUNET_TIME_UNIT_ZERO_ABS,
+                                 GNUNET_TIME_UNIT_MICROSECONDS),
+       /* zero results expected, time too ancient. */
+       0,
+       10,
+       10),
+
+    TALER_TESTING_cmd_refund_increase ("refund-increase-1",
+                                       merchant_url,
+                                       is->ctx,
+                                       "refund test",
+                                       "1",
+                                       "EUR:0.1",
+                                       "EUR:0.01"),
+
+    TALER_TESTING_cmd_refund_lookup ("refund-lookup-1",
+                                     merchant_url,
+                                     is->ctx,
+                                     "refund-increase-1",
+                                     "deposit-simple",
+                                     "1"),
+    /* test tipping */
+    TALER_TESTING_cmd_fakebank_transfer_with_instance
+      ("create-reserve-10",
+       "EUR:10.02",
+       fakebank_url,
+       USER_ACCOUNT_NO,
+       EXCHANGE_ACCOUNT_NO,
+       USER_LOGIN_NAME,
+       USER_LOGIN_PASS,
+       "tip",
+       EXCHANGE_URL,
+       CONFIG_FILE),
+
+
+    CMD_EXEC_WIREWATCH ("wirewatch-10"),
+
+    TALER_TESTING_cmd_check_bank_transfer
+      ("check_bank_transfer-10",
+       "http://localhost:8081/";,
+       "EUR:10.02", USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO),
+
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10a",
+                                       is->exchange,
+                                       "create-reserve-10",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-10b",
+                                       is->exchange,
+                                       "create-reserve-10",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+
+
+    TALER_TESTING_cmd_status ("withdraw-status-10",
+                              is->exchange,
+                              "create-reserve-10",
+                              "EUR:0",
+                              MHD_HTTP_OK),
+
+
+    TALER_TESTING_cmd_proposal
+      ("create-proposal-10",
+       merchant_url,
+       is->ctx,
+       MHD_HTTP_OK,
+       "{\"max_fee\":\
+          {\"currency\":\"EUR\",\
+           \"value\":0,\
+           \"fraction\":50000000},\
+        \"order_id\":\"10\",\
+        \"refund_deadline\":\"\\/Date(0)\\/\",\
+        \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
+        \"amount\":\
+          {\"currency\":\"EUR\",\
+           \"value\":10,\
+           \"fraction\":0},\
+        \"summary\": \"merchant-lib testcase\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:10}\"} ] }",
+        NULL),
+
+    TALER_TESTING_cmd_pay ("pay-fail-partial-double-10",
+                           merchant_url,
+                           is->ctx,
+                           MHD_HTTP_FORBIDDEN,
+                           "create-proposal-10",
+                           "withdraw-coin-10a;withdraw-coin-1",
+                           "EUR:5",
+                           "EUR:4.99",
+                           "EUR:0.01"),
+
+    TALER_TESTING_cmd_pay_again
+      ("pay-again-10",
+       merchant_url,
+       "pay-fail-partial-double-10",
+       "withdraw-coin-10a;withdraw-coin-10b",
+       "EUR:0.01",
+       is->ctx,
+       MHD_HTTP_OK),
+
+    CMD_EXEC_AGGREGATOR ("run-aggregator-10"),
+
+    TALER_TESTING_cmd_check_bank_transfer
+      ("check_bank_transfer-9.97-10",
+       "http://localhost:8081/";,
+       "EUR:9.97",
+       EXCHANGE_ACCOUNT_NO,
+       USER_ACCOUNT_NO),
+
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-10"),
+
+    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-11",
+                              "EUR:10.02"),
+
+    CMD_EXEC_WIREWATCH ("wirewatch-11"),
+
+    TALER_TESTING_cmd_check_bank_transfer
+      ("check_bank_transfer-11",
+       "http://localhost:8081/";,
+       "EUR:10.02", USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO),
+
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-11a",
+                                       is->exchange,
+                                       "create-reserve-11",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+
+    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-11b",
+                                       is->exchange,
+                                       "create-reserve-11",
+                                       "EUR:5",
+                                       MHD_HTTP_OK),
+
+    TALER_TESTING_cmd_status ("withdraw-status-11",
+                              is->exchange,
+                              "create-reserve-11",
+                              "EUR:0",
+                              MHD_HTTP_OK),
+
+    TALER_TESTING_cmd_proposal
+      ("create-proposal-11",
+       merchant_url,
+       is->ctx,
+       MHD_HTTP_OK,
+       "{\"max_fee\":\
+          {\"currency\":\"EUR\",\
+           \"value\":0,\
+           \"fraction\":50000000},\
+        \"order_id\":\"11\",\
+        \"refund_deadline\":\"\\/Date(0)\\/\",\
+        \"pay_deadline\":\"\\/Date(99999999999)\\/\",\
+        \"amount\":\
+          {\"currency\":\"EUR\",\
+           \"value\":10,\
+           \"fraction\":0},\
+        \"summary\": \"merchant-lib testcase\",\
+        \"products\": [ {\"description\":\"ice cream\",\
+                         \"value\":\"{EUR:10}\"} ] }",
+        NULL),
+
+    TALER_TESTING_cmd_pay ("pay-fail-partial-double-11",
+                           merchant_url,
+                           is->ctx,
+                           MHD_HTTP_FORBIDDEN,
+                           "create-proposal-11",
+                           "withdraw-coin-11a;withdraw-coin-1",
+                           "EUR:5",
+                           "EUR:4.99",
+                           "EUR:0.01"),
+
+    TALER_TESTING_cmd_pay_abort ("pay-abort-11",
+                                 merchant_url,
+                                 "pay-fail-partial-double-11",
+                                 is->ctx,
+                                 MHD_HTTP_OK),
+
+    TALER_TESTING_cmd_pay_abort_refund ("pay-abort-refund-11",
+                                        is->exchange,
+                                        /* abort reference */
+                                        "pay-abort-11",
+                                        0,
+                                        "EUR:5",
+                                        "EUR:0.01",
+                                        MHD_HTTP_OK),
+
+    CMD_EXEC_AGGREGATOR ("run-aggregator-11"),
+
+    TALER_TESTING_cmd_check_bank_empty ("check_bank_empty-11"),
+
+    /**
+     * End the suite.  Fixme: better to have a label for this
+     * too, as it shows a "(null)" token on logs.
+     */
+    TALER_TESTING_cmd_end ()
+  };
+
+  TALER_TESTING_run_with_fakebank (is,
+                                   commands,
+                                   fakebank_url);
+}
+
+int
+main (int argc,
+      char * const *argv)
+{
+  unsigned int ret;
+  /* These environment variables get in the way... */
+  unsetenv ("XDG_DATA_HOME");
+  unsetenv ("XDG_CONFIG_HOME");
+  GNUNET_log_setup ("test-merchant-api-new",
+                    "DEBUG",
+                    NULL);
+
+  if (NULL ==
+     (fakebank_url = TALER_TESTING_prepare_fakebank (
+       CONFIG_FILE)))
+    return 77;
+
+  if (NULL ==
+     (merchant_url = TALER_TESTING_prepare_merchant (
+       CONFIG_FILE)))
+    return 77;
+
+  TALER_TESTING_cleanup_files (CONFIG_FILE);
+
+
+  switch (TALER_TESTING_prepare_exchange (CONFIG_FILE))
+  {
+  case GNUNET_SYSERR:
+    GNUNET_break (0);
+    return 1;
+  case GNUNET_NO:
+    return 77;
+
+  case GNUNET_OK:
+
+    if (NULL == (merchantd =
+        TALER_TESTING_run_merchant (CONFIG_FILE)))
+      return 1;
+
+    ret = TALER_TESTING_setup_with_exchange (&run,
+                                             NULL,
+                                             CONFIG_FILE);
+    GNUNET_OS_process_kill (merchantd, SIGKILL); 
+    GNUNET_OS_process_wait (merchantd); 
+    GNUNET_OS_process_destroy (merchantd); 
+    GNUNET_free (merchant_url);
+
+    if (GNUNET_OK != ret)
+      return 1;
+    break;
+  default:
+    GNUNET_break (0);
+    return 1;
+  }
+  return 0;
+}
+
+/* end of test_merchant_api_new.c */
diff --git a/src/lib/testing_api_cmd_history.c 
b/src/lib/testing_api_cmd_history.c
new file mode 100644
index 0000000..3667d34
--- /dev/null
+++ b/src/lib/testing_api_cmd_history.c
@@ -0,0 +1,302 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 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 lib/testing_api_cmd_history.c
+ * @brief command to test the /history API.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+struct HistoryState
+{
+
+  /**
+   * Expected status code.
+   */
+  unsigned int http_status;
+
+  /**
+   * The merchant instance.
+   */
+  const char *instance;
+
+  /**
+   * URL of the merchant backend.
+   */
+  const char *merchant_url;
+
+  /**
+   * The curl context; used to be fed to the merchant lib.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Handle to /history.
+   */
+  struct TALER_MERCHANT_HistoryOperation *ho;
+
+  /**
+   * FIXME
+   */
+  struct GNUNET_TIME_Absolute time;
+
+  /**
+   * FIXME
+   */
+  unsigned int start;
+
+  /**
+   * FIXME
+   */
+  unsigned int nrows;
+
+  /**
+   * FIXME
+   */
+  unsigned int nresult;
+};
+
+/**
+ * Parse given JSON object to absolute time.
+ *
+ * @param root the json object representing data
+ * @param[out] ret where to write the data
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
+ */
+static int
+parse_abs_time (json_t *root,
+                struct GNUNET_TIME_Absolute *ret)
+{
+  const char *val;
+  unsigned long long int tval;
+
+  val = json_string_value (root);
+  if (NULL == val)
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  if ( (0 == strcasecmp (val,
+                         "/forever/")) ||
+       (0 == strcasecmp (val,
+                         "/end of time/")) ||
+       (0 == strcasecmp (val,
+                         "/never/")) )
+  {
+    *ret = GNUNET_TIME_UNIT_FOREVER_ABS;
+    return GNUNET_OK;
+  }
+  if (1 != sscanf (val,
+                   "/Date(%llu)/",
+                   &tval))
+  {
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  /* Time is in seconds in JSON, but in microseconds in GNUNET_TIME_Absolute */
+  ret->abs_value_us = tval * 1000LL * 1000LL;
+  if ( (ret->abs_value_us) / 1000LL / 1000LL != tval)
+  {
+    /* Integer overflow */
+    GNUNET_break_op (0);
+    return GNUNET_SYSERR;
+  }
+  return GNUNET_OK;
+}
+
+
+/**
+ * Callback for a /history request. It's up to this function
+ * how to render the array containing transactions details (FIXME
+ * link to documentation)
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant
+ *        backend
+ * @param ec taler-specific error code
+ * @param json actual body containing history
+ */
+static void
+history_cb (void *cls,
+            unsigned int http_status,
+            enum TALER_ErrorCode ec,
+            const json_t *json)
+{
+
+  struct HistoryState *hs = cls;
+  unsigned int nresult;
+  struct GNUNET_TIME_Absolute last_timestamp;
+  struct GNUNET_TIME_Absolute entry_timestamp;
+
+  hs->ho = NULL;
+  if (hs->http_status != http_status)
+    TALER_TESTING_FAIL (hs->is);
+
+  nresult = json_array_size (json);
+  if (hs->nresult != nresult)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected number of history entries."
+                " Got %d, expected %d\n",
+                nresult,
+                hs->nresult);
+    TALER_TESTING_FAIL (hs->is);
+  }
+
+  last_timestamp = GNUNET_TIME_absolute_get ();
+  last_timestamp = GNUNET_TIME_absolute_add
+    (last_timestamp, GNUNET_TIME_UNIT_DAYS);
+  json_t *entry;
+  json_t *timestamp;
+  size_t index;
+  json_array_foreach (json, index, entry)
+  {
+    timestamp = json_object_get (entry, "timestamp");
+    if (GNUNET_OK != parse_abs_time (timestamp, &entry_timestamp))
+      TALER_TESTING_FAIL (hs->is);
+
+    if (last_timestamp.abs_value_us < entry_timestamp.abs_value_us)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "History entries are NOT"
+                  " sorted from younger to older\n");
+      TALER_TESTING_interpreter_fail (hs->is);
+      return;
+    }
+
+    last_timestamp = entry_timestamp;
+  }
+
+  TALER_TESTING_interpreter_next (hs->is);
+}
+
+/**
+ * Clean up after the command.  Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+history_cleanup (void *cls,
+                 const struct TALER_TESTING_Command *cmd)
+{
+  struct HistoryState *hs = cls;
+
+  if (NULL != hs->ho)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "/history operation did not complete\n");
+    TALER_MERCHANT_history_cancel (hs->ho);
+  }
+  GNUNET_free (hs);
+}
+
+/**
+ * Runs the command.  Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks.  Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @param is interpreter state
+ */
+static void
+history_run (void *cls,
+             const struct TALER_TESTING_Command *cmd,
+             struct TALER_TESTING_Interpreter *is)
+{
+  struct HistoryState *hs = cls;
+  
+  hs->is = is;
+  if (0 == hs->time.abs_value_us)
+  {
+    hs->time = GNUNET_TIME_absolute_add
+      (GNUNET_TIME_absolute_get (),
+       GNUNET_TIME_UNIT_HOURS);
+    GNUNET_TIME_round_abs (&hs->time);
+  }
+  if ( NULL ==
+     ( hs->ho = TALER_MERCHANT_history (hs->ctx,
+                                        hs->merchant_url,
+                                        "default",
+                                        hs->start,
+                                        hs->nrows,
+                                        hs->time,
+                                        &history_cb,
+                                        hs)))
+  TALER_TESTING_FAIL (is);
+}
+
+
+/**
+ * Make a "history" command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param ctx main CURL context
+ * @param http_status expected HTTP response code
+ * @param time FIXME
+ * @param nresult how many results are expected
+ * @param start FIXME.
+ * @param nrows how many row we want to receive, at most.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_history (const char *label,
+                           const char *merchant_url,
+                           struct GNUNET_CURL_Context *ctx,
+                           unsigned int http_status,
+                           struct GNUNET_TIME_Absolute time,
+                           unsigned int nresult,
+                           unsigned int start,
+                           unsigned int nrows)
+{
+  struct HistoryState *hs;
+  struct TALER_TESTING_Command cmd;
+
+  hs = GNUNET_new (struct HistoryState);
+  hs->http_status = http_status;
+  hs->time = time;
+  hs->nresult = nresult;
+  hs->start = start;
+  hs->nrows = nrows;
+  hs->merchant_url = merchant_url;
+  hs->ctx = ctx;
+
+  cmd.cls = hs;
+  cmd.label = label;
+  cmd.run = &history_run;
+  cmd.cleanup = &history_cleanup;
+  
+  return cmd;
+}
+
+/* end of testing_api_cmd_history.c */
diff --git a/src/lib/testing_api_cmd_pay.c b/src/lib/testing_api_cmd_pay.c
new file mode 100644
index 0000000..c7f3e87
--- /dev/null
+++ b/src/lib/testing_api_cmd_pay.c
@@ -0,0 +1,1538 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 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 lib/testing_api_cmd_pay.c
+ * @brief command to test the /pay feature.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.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_testing_lib.h"
+
+#define AMOUNT_WITH_FEE 0
+#define AMOUNT_WITHOUT_FEE 1
+#define REFUND_FEE 2
+
+struct PayState
+{
+  /**
+   * Contract terms hash code.
+   */
+  struct GNUNET_HashCode 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;
+
+  /**
+   * The curl context; used to be fed to the merchant lib.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * The merchant base URL.
+   */
+  const char *merchant_url;
+
+  /**
+   * 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;
+
+  /**
+   * Fee for refunding this payment.
+   */
+  const char *refund_fee;
+
+  /**
+   * Handle to the /pay operation.
+   */
+  struct TALER_MERCHANT_Pay *po;
+
+};
+
+
+struct CheckPaymentState
+{
+
+  /**
+   * Operation handle.
+   */
+  struct TALER_MERCHANT_CheckPaymentOperation *cpo;
+
+  /**
+   * 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;
+
+  /**
+   * GNUNET_YES if we expect the proposal was paid.
+   */
+  unsigned int expect_paid;
+
+  /**
+   * The curl context; used to be fed to the merchant lib.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * The merchant base URL.
+   */
+  const char *merchant_url;
+
+};
+
+
+struct PayAgainState
+{
+  
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * Reference to the "pay" command to abort.
+   */
+  const char *pay_reference;
+
+  /**
+   * Reference to the coins to use.
+   */
+  const char *coin_reference;
+
+  /**
+   * Main CURL context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Merchant URL.
+   */
+  const char *merchant_url;
+
+  /**
+   * Refund fee.
+   */
+  const char *refund_fee;
+
+  /**
+   * Handle to a "pay again" operation.
+   */
+  struct TALER_MERCHANT_Pay *pao;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+};
+
+
+struct PayAbortState
+{
+  
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  /**
+   * Reference to the "pay" command to abort.
+   */
+  const char *pay_reference;
+
+  /**
+   * Main CURL context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Merchant URL.
+   */
+  const char *merchant_url;
+
+  /**
+   * Handle to a "pay abort" operation.
+   */
+  struct TALER_MERCHANT_Pay *pao;
+
+  /**
+   * Interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+
+  /**
+   * FIXME.
+   */
+  unsigned int num_refunds;
+
+  /**
+   * FIXME.
+   */
+  struct TALER_MERCHANT_RefundEntry *res;
+
+  /**
+   * FIXME.
+   */
+  struct GNUNET_HashCode h_contract;
+
+  /**
+   * Merchant public key.
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+};
+
+
+struct PayAbortRefundState
+{
+  const char *abort_reference;
+
+  unsigned int num_coins;
+
+  const char *refund_amount;
+
+  const char *refund_fee;
+
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Handle to the refund operation.
+   */
+  struct TALER_EXCHANGE_RefundHandle *rh;
+
+  /**
+   * Expected HTTP response code.
+   */
+  unsigned int http_status;
+
+  struct TALER_EXCHANGE_Handle *exchange;
+};
+
+
+
+/**
+ * Clean up after the command.  Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+check_payment_cleanup (void *cls,
+                       const struct TALER_TESTING_Command *cmd)
+{
+  struct CheckPaymentState *cps = cls;
+
+  if (NULL != cps->cpo)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Command `%s' was not terminated\n",
+                TALER_TESTING_interpreter_get_current_label (
+                  cps->is));
+    TALER_MERCHANT_check_payment_cancel (cps->cpo);  
+  }
+  GNUNET_free (cps);
+}
+
+
+/**
+ * Callback for GET /proposal issued at backend. Just check
+ * whether response code is as expected.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code we got
+ * @param json full response we got
+ */
+static void
+check_payment_cb (void *cls,
+                  unsigned int http_status,
+                  const json_t *obj,
+                  int paid,
+                  int refunded,
+                  struct TALER_Amount *refund_amount,
+                  const char *payment_redirect_url)
+{
+  struct CheckPaymentState *cps = cls;
+
+  cps->cpo = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "check payment: expected paid: %s: %d\n",
+              TALER_TESTING_interpreter_get_current_label (
+                cps->is),
+              cps->expect_paid);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "check payment: paid: %d\n",
+              paid);
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "check payment: url: %s\n",
+              payment_redirect_url);
+
+  if (paid != cps->expect_paid)
+    TALER_TESTING_FAIL (cps->is);
+
+  if (cps->http_status != http_status)
+    TALER_TESTING_FAIL (cps->is);
+
+  TALER_TESTING_interpreter_next (cps->is);
+}
+
+/**
+ * Runs the command.  Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks.  Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @param is interpreter state
+ */
+static void
+check_payment_run (void *cls,
+                   const struct TALER_TESTING_Command *cmd,
+                   struct TALER_TESTING_Interpreter *is)
+{
+  struct CheckPaymentState *cps = cls;
+  const struct TALER_TESTING_Command *proposal_cmd;
+  const char *order_id;
+
+  cps->is = is;
+  proposal_cmd = TALER_TESTING_interpreter_lookup_command (
+    is, cps->proposal_reference);
+
+  if (NULL == proposal_cmd)
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_order_id (
+    proposal_cmd, 0, &order_id))
+    TALER_TESTING_FAIL (is);
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Checking for order id `%s'\n",
+              order_id);
+
+  cps->cpo = TALER_MERCHANT_check_payment (
+    cps->ctx,
+    cps->merchant_url,
+    "default", // only default instance for now.
+    order_id,
+    NULL,
+    NULL,
+    NULL,
+    check_payment_cb,
+    cps);
+
+  GNUNET_assert (NULL != cps->cpo); 
+}
+
+/**
+ * Make a "check payment" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param ctx CURL context.
+ * @param http_status expected HTTP response code.
+ * @param proposal_reference the proposal whose payment status
+ *        is going to be checked.
+ * @param expect_paid GNUNET_YES if we expect the proposal to be
+ *        paid, GNUNET_NO otherwise.
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_check_payment (
+  const char *label,
+  const char *merchant_url,
+  struct GNUNET_CURL_Context *ctx,
+  unsigned int http_status,
+  const char *proposal_reference,
+  unsigned int expect_paid)
+{
+  struct CheckPaymentState *cps;
+  struct TALER_TESTING_Command cmd;
+
+  cps = GNUNET_new (struct CheckPaymentState);
+  cps->http_status = http_status;
+  cps->proposal_reference = proposal_reference;
+  cps->expect_paid = expect_paid;
+  cps->ctx = ctx;
+  cps->merchant_url = merchant_url;
+
+  cmd.cls = cps;
+  cmd.label = label;
+  cmd.run = &check_payment_run;
+  cmd.cleanup = &check_payment_cleanup;
+
+  return cmd;
+
+}
+
+/**
+ * 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
+ * @param amount_without_fee
+ * @param refund_fee
+ * @return #GNUNET_OK on success
+ */
+static int
+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,
+             const char *refund_fee)
+{
+  char *token;
+
+  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;
+
+    /* Token syntax is "LABEL[/NUMBER]" */
+    ctok = strchr (token, '/');
+    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];
+
+    struct TALER_CoinSpendPrivateKeyP *coin_priv; 
+    const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
+    struct TALER_DenominationSignature *denom_sig;
+    struct TALER_Amount *denom_value;
+
+    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_obj
+        (coin_cmd, 0, &denom_value));
+
+    icoin->coin_priv = *coin_priv;
+    icoin->denom_pub = denom_pub->key;
+    icoin->denom_sig = *denom_sig;
+    icoin->denom_value = *denom_value;
+
+    GNUNET_assert
+      (GNUNET_OK == TALER_TESTING_get_trait_url
+        (coin_cmd, 0, &icoin->exchange_url)); 
+
+    GNUNET_assert
+      (GNUNET_OK == TALER_string_to_amount
+        (amount_with_fee, &icoin->amount_with_fee));
+
+    GNUNET_assert
+      (GNUNET_OK == TALER_string_to_amount
+        (amount_without_fee, &icoin->amount_without_fee));
+
+    GNUNET_assert
+      (GNUNET_OK == TALER_string_to_amount
+        (refund_fee, &icoin->refund_fee));
+  }
+
+  return GNUNET_OK;
+}
+
+
+/**
+ * Function called with the result of a /pay operation.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200)
+ *        for successful deposit; 0 if the exchange's reply is
+ *        bogus (fails to follow the protocol)
+ * @param ec taler-specific error object
+ * @param obj the received JSON reply, should be kept as proof
+ *        (and, in case of errors, be forwarded to the customer)
+ */
+static void
+pay_cb (void *cls,
+        unsigned int http_status,
+        enum TALER_ErrorCode ec,
+        const json_t *obj)
+{
+  struct PayState *ps = cls;
+
+  struct GNUNET_CRYPTO_EddsaSignature sig;
+  const char *error_name;
+  unsigned int error_line;
+  const struct GNUNET_CRYPTO_EddsaPublicKey *merchant_pub;
+  const struct TALER_TESTING_Command *proposal_cmd;
+
+  ps->po = NULL;
+  if (ps->http_status != http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                http_status,
+                ec,
+                TALER_TESTING_interpreter_get_current_label (
+                  ps->is));
+    TALER_TESTING_FAIL (ps->is);
+  }
+  if (MHD_HTTP_OK == http_status)
+  {
+    /* Check signature */
+    struct PaymentResponsePS mr;
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_fixed_auto ("sig",
+                                   &sig),
+      GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+                                  &ps->h_contract_terms),
+      GNUNET_JSON_spec_end ()
+    };
+
+    GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (
+      obj, spec,
+      &error_name,
+      &error_line));
+
+    mr.purpose.purpose = htonl (
+      TALER_SIGNATURE_MERCHANT_PAYMENT_OK);
+    mr.purpose.size = htonl (sizeof (mr));
+    mr.h_contract_terms = ps->h_contract_terms;
+
+    /* proposal reference was used at least once, at this point */
+    GNUNET_assert
+      ( NULL !=
+      ( proposal_cmd = TALER_TESTING_interpreter_lookup_command
+      (ps->is, ps->proposal_reference)));
+
+    if (GNUNET_OK != TALER_TESTING_get_trait_peer_key_pub
+        (proposal_cmd, 0, &merchant_pub))
+      TALER_TESTING_FAIL (ps->is);
+
+    if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (
+      TALER_SIGNATURE_MERCHANT_PAYMENT_OK,
+      &mr.purpose, &sig,
+      merchant_pub))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Merchant signature given in response to /pay"
+                  " invalid\n");
+      TALER_TESTING_FAIL (ps->is);
+    }
+  }
+
+  TALER_TESTING_interpreter_next (ps->is);
+}
+
+/**
+ * FIXME
+ */
+static void
+pay_abort_cb (void *cls,
+              unsigned int http_status,
+              enum TALER_ErrorCode ec,
+              const struct TALER_MerchantPublicKeyP *merchant_pub,
+              const struct GNUNET_HashCode *h_contract,
+              unsigned int num_refunds,
+              const struct TALER_MERCHANT_RefundEntry *res,
+              const json_t *obj)
+{
+  struct PayAbortState *pas = cls;
+
+  pas->pao = NULL;
+  if (pas->http_status != http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                http_status,
+                ec,
+                TALER_TESTING_interpreter_get_current_label
+                  (pas->is));
+    TALER_TESTING_FAIL (pas->is);
+  }
+  if ( (MHD_HTTP_OK == http_status) &&
+       (TALER_EC_NONE == ec) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Received %u refunds\n",
+                num_refunds);
+    pas->num_refunds = num_refunds;
+
+    pas->res = GNUNET_new_array
+      (num_refunds, struct TALER_MERCHANT_RefundEntry);
+
+    memcpy (pas->res, res,
+           num_refunds * sizeof
+              (struct TALER_MERCHANT_RefundEntry));
+    pas->h_contract = *h_contract;
+    pas->merchant_pub = *merchant_pub;
+  }
+
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Successful pay-abort (HTTP status: %u)\n",
+              http_status);
+  TALER_TESTING_interpreter_next (pas->is);
+}
+
+
+/**
+ * @param cls the closure.  Will be a "pay" or "pay-abort" state,
+ *        depending on whether the caller was "pay" or "pay abort"
+ *        run method.
+ *
+ * @return handle to the operation, NULL if errors occur.
+ */
+static struct TALER_MERCHANT_Pay *
+_pay_run (const char *merchant_url,
+          struct GNUNET_CURL_Context *ctx,
+          const char *coin_reference,
+          const char *proposal_reference,
+          struct TALER_TESTING_Interpreter *is,
+          const char *amount_with_fee,
+          const char *amount_without_fee,
+          const char *refund_fee,
+          struct TALER_MERCHANT_Pay * (*api_func) (),
+          void (*api_cb) (),
+          void *cls)
+{
+  const struct TALER_TESTING_Command *proposal_cmd;
+  const char *contract_terms;
+  json_t *ct;
+  const char *order_id;
+  struct GNUNET_TIME_Absolute refund_deadline;
+  struct GNUNET_TIME_Absolute pay_deadline;
+  struct GNUNET_TIME_Absolute timestamp;
+  struct TALER_MerchantPublicKeyP merchant_pub;
+  struct GNUNET_HashCode h_wire;
+  const struct GNUNET_HashCode *h_proposal;
+  struct TALER_Amount total_amount;
+  struct TALER_Amount max_fee;
+  const char *error_name;
+  unsigned int error_line;
+  struct TALER_MERCHANT_PayCoin *pay_coins;
+  unsigned int npay_coins;
+  char *cr;
+  struct TALER_MerchantSignatureP *merchant_sig;
+  struct TALER_MERCHANT_Pay *ret;
+
+  proposal_cmd = TALER_TESTING_interpreter_lookup_command
+    (is, proposal_reference);
+
+  if (NULL == proposal_cmd)
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_contract_terms
+    (proposal_cmd, 0, &contract_terms))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+
+  json_error_t error;
+  if (NULL ==
+     (ct = json_loads (contract_terms,
+                       JSON_COMPACT,
+                       &error)))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+  /* Get information that needs to be put verbatim in the
+   * deposit permission */
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_string ("order_id",
+                             &order_id),
+    GNUNET_JSON_spec_absolute_time ("refund_deadline",
+                                    &refund_deadline),
+    GNUNET_JSON_spec_absolute_time ("pay_deadline",
+                                    &pay_deadline),
+    GNUNET_JSON_spec_absolute_time ("timestamp",
+                                    &timestamp),
+    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                 &merchant_pub),
+    GNUNET_JSON_spec_fixed_auto ("H_wire",
+                                 &h_wire),
+    TALER_JSON_spec_amount ("amount",
+                            &total_amount),
+    TALER_JSON_spec_amount ("max_fee",
+                            &max_fee),
+    GNUNET_JSON_spec_end()
+  };
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (ct,
+                         spec,
+                         &error_name,
+                         &error_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Parser failed on %s:%u\n",
+                error_name,
+                error_line);
+    fprintf (stderr, "%s\n", contract_terms);
+    GNUNET_break_op (0);
+    return NULL;
+  }
+
+  cr = GNUNET_strdup (coin_reference);
+  pay_coins = NULL;
+  npay_coins = 0;
+  if (GNUNET_OK !=
+      build_coins (&pay_coins,
+                   &npay_coins,
+                  cr,
+                   is,
+                   amount_with_fee,
+                   amount_without_fee,
+                   refund_fee))
+  {
+    GNUNET_array_grow (pay_coins,
+                       npay_coins,
+                      0);
+    GNUNET_free (cr);
+    GNUNET_break (0);
+    return NULL;
+  }
+
+  GNUNET_free (cr);
+  if (GNUNET_OK != TALER_TESTING_get_trait_merchant_sig
+    (proposal_cmd, 0, &merchant_sig))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_h_contract_terms
+    (proposal_cmd, 0, &h_proposal))
+  {
+    GNUNET_break (0);
+    return NULL;
+  }
+
+  ret = api_func (ctx,
+                  merchant_url,
+                  "default", // instance
+                  h_proposal,
+                  &total_amount,
+                  &max_fee,
+                  &merchant_pub,
+                  merchant_sig,
+                  timestamp,
+                  refund_deadline,
+                  pay_deadline,
+                  &h_wire,
+                  order_id,
+                  npay_coins,
+                  pay_coins,
+                  api_cb,
+                  cls);
+
+  GNUNET_array_grow (pay_coins,
+                     npay_coins,
+                     0);
+  return ret;
+}
+
+/**
+ * Runs the command.  Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks.  Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @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;
+  
+  ps->is = is;
+  if ( NULL == 
+     ( ps->po = _pay_run (ps->merchant_url,
+                          ps->ctx,
+                          ps->coin_reference,
+                          ps->proposal_reference,
+                          is,
+                          ps->amount_with_fee,
+                          ps->amount_without_fee,
+                          ps->refund_fee,
+                          &TALER_MERCHANT_pay_wallet,
+                          &pay_cb,
+                          ps)) )
+    TALER_TESTING_FAIL (is);
+}
+
+/**
+ * Clean up after the command.  Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+pay_cleanup (void *cls,
+             const struct TALER_TESTING_Command *cmd)
+{
+  struct PayState *ps = cls;
+
+  if (NULL != ps->po)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Command `%s' did not complete.\n",
+                TALER_TESTING_interpreter_get_current_label (
+                  ps->is));
+    TALER_MERCHANT_pay_cancel (ps->po);
+  }
+
+  GNUNET_free (ps);
+}
+
+
+/**
+ * Extract information from a command that is useful for other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param selector more detailed information about which object
+ *                 to return in case there were multiple generated
+ *                 by the command
+ * @return #GNUNET_OK on success
+ */
+static int
+pay_traits (void *cls,
+            void **ret,
+            const char *trait,
+            unsigned int index)
+{
+
+  struct PayState *ps = cls;
+  const char *order_id;
+  const struct TALER_TESTING_Command *proposal_cmd;
+  struct GNUNET_CRYPTO_EddsaPublicKey *merchant_pub;
+
+  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, 0, &order_id))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_peer_key_pub
+      (proposal_cmd,
+       0,
+       (const struct GNUNET_CRYPTO_EddsaPublicKey **)
+         &merchant_pub))
+  {
+    GNUNET_break (0);
+    return GNUNET_SYSERR;
+  }
+
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_amount
+      (AMOUNT_WITH_FEE, ps->amount_with_fee),
+    TALER_TESTING_make_trait_amount
+      (AMOUNT_WITHOUT_FEE, ps->amount_without_fee),
+    TALER_TESTING_make_trait_amount
+      (REFUND_FEE, ps->refund_fee),
+    TALER_TESTING_make_trait_proposal_reference
+      (0, ps->proposal_reference),
+    TALER_TESTING_make_trait_coin_reference
+      (0, ps->coin_reference),
+    TALER_TESTING_make_trait_order_id (0, order_id),
+    TALER_TESTING_make_trait_peer_key_pub (0, merchant_pub),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+
+  return GNUNET_SYSERR;
+}
+
+/**
+ * Make a "pay" test command.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param ctx CURL context.
+ * @param http_status expected HTTP response code.
+ * @param proposal_reference the proposal whose payment status
+ *        is going to be checked.
+ * @param coin_reference reference to any command which is able
+ *        to provide coins to use for paying.
+ * @param amount_with_fee amount to pay, including the deposit
+ *        fee
+ * @param amount_without_fee amount to pay, no fees included.
+ * @param refund_fee fee for refunding this payment.
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay (
+  const char *label,
+  const char *merchant_url,
+  struct GNUNET_CURL_Context *ctx,
+  unsigned int http_status,
+  const char *proposal_reference,
+  const char *coin_reference,
+  const char *amount_with_fee,
+  const char *amount_without_fee,
+  const char *refund_fee)
+{
+  struct PayState *ps;
+  struct TALER_TESTING_Command cmd;
+
+  ps = GNUNET_new (struct PayState);
+  ps->http_status = http_status;
+  ps->proposal_reference = proposal_reference;
+  ps->coin_reference = coin_reference;
+  ps->ctx = ctx;
+  ps->merchant_url = merchant_url;
+  ps->amount_with_fee = amount_with_fee;
+  ps->amount_without_fee = amount_without_fee;
+  ps->refund_fee = refund_fee;
+
+  cmd.cls = ps;
+  cmd.label = label;
+  cmd.run = &pay_run;
+  cmd.cleanup = &pay_cleanup;
+  cmd.traits = &pay_traits;
+
+  return cmd;
+
+}
+
+
+/**
+ * Clean up after the command.  Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+pay_abort_cleanup (void *cls,
+                   const struct TALER_TESTING_Command *cmd)
+{
+  struct PayAbortState *pas = cls;
+
+  if (NULL != pas->pao)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Command `%s' did not complete.\n",
+                TALER_TESTING_interpreter_get_current_label (
+                  pas->is));
+    TALER_MERCHANT_pay_cancel (pas->pao);
+  }
+
+  GNUNET_free (pas);
+}
+
+/**
+ * Runs the command.  Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks.  Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @param is interpreter state
+ */
+static void
+pay_abort_run (void *cls,
+               const struct TALER_TESTING_Command *cmd,
+               struct TALER_TESTING_Interpreter *is)
+{
+  
+  struct PayAbortState *pas = cls;
+  const struct TALER_TESTING_Command *pay_cmd;
+
+  const char *proposal_reference;
+  const char *coin_reference;
+  const char *amount_with_fee;
+  const char *amount_without_fee;
+  const char *refund_fee;
+  
+  pas->is = is;
+  pay_cmd = TALER_TESTING_interpreter_lookup_command
+    (is, pas->pay_reference);
+  if (NULL == pay_cmd)
+    TALER_TESTING_FAIL (is);
+  
+  if (GNUNET_OK != TALER_TESTING_get_trait_proposal_reference
+      (pay_cmd, 0, &proposal_reference))
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_coin_reference
+      (pay_cmd, 0, &coin_reference))
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_amount
+    (pay_cmd, AMOUNT_WITH_FEE, &amount_with_fee))
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_amount
+    (pay_cmd, AMOUNT_WITHOUT_FEE, &amount_without_fee))
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_amount
+    (pay_cmd, REFUND_FEE, &refund_fee))
+    TALER_TESTING_FAIL (is);
+
+  if ( NULL ==
+     ( pas->pao = _pay_run (pas->merchant_url,
+                            pas->ctx,
+                            coin_reference,
+                            proposal_reference,
+                            is,
+                            amount_with_fee,
+                            amount_without_fee,
+                            refund_fee,
+                            &TALER_MERCHANT_pay_abort,
+                            &pay_abort_cb,
+                            pas)) )
+    TALER_TESTING_FAIL (is);
+}
+
+/**
+ * Extract information from a command that is useful for other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param selector more detailed information about which object
+ *                 to return in case there were multiple generated
+ *                 by the command
+ * @return #GNUNET_OK on success
+ */
+static int
+pay_abort_traits (void *cls,
+                  void **ret,
+                  const char *trait,
+                  unsigned int index)
+{
+  struct PayAbortState *pas = cls;
+
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_peer_key_pub
+      (0, &pas->merchant_pub.eddsa_pub),
+    TALER_TESTING_make_trait_h_contract_terms
+      (0, &pas->h_contract),
+    TALER_TESTING_make_trait_refund_entry
+      (0, pas->res),
+    TALER_TESTING_make_trait_uint (0, &pas->num_refunds),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+
+  return GNUNET_SYSERR;
+}
+
+/**
+ * Make a "pay abort" test command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param pay_reference reference to the payment to abort
+ * @param ctx main CURL context
+ * @param http_status expected HTTP response code
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay_abort (const char *label,
+                             const char *merchant_url,
+                             const char *pay_reference,
+                             struct GNUNET_CURL_Context *ctx,
+                             unsigned int http_status)
+{
+  struct PayAbortState *pas;
+  struct TALER_TESTING_Command cmd;
+
+  pas = GNUNET_new (struct PayAbortState);
+  pas->http_status = http_status;
+  pas->pay_reference = pay_reference;
+  pas->ctx = ctx;
+  pas->merchant_url = merchant_url;
+
+  cmd.cls = pas;
+  cmd.label = label;
+  cmd.run = &pay_abort_run;
+  cmd.cleanup = &pay_abort_cleanup;
+  cmd.traits = &pay_abort_traits;
+  
+  return cmd;
+}
+
+/**
+ * Function called with the result of a /pay again operation.
+ *
+ * @param cls closure with the interpreter state
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200)
+ *        for successful deposit; 0 if the exchange's reply is
+ *        bogus (fails to follow the protocol)
+ * @param ec taler-specific error object
+ * @param obj the received JSON reply, should be kept as proof
+ *        (and, in case of errors, be forwarded to the customer)
+ */
+static void
+pay_again_cb (void *cls,
+             unsigned int http_status,
+             enum TALER_ErrorCode ec,
+             const json_t *obj)
+{
+  struct PayAgainState *pas = cls;
+  struct GNUNET_CRYPTO_EddsaSignature sig;
+  const char *error_name;
+  unsigned int error_line;
+  const struct TALER_TESTING_Command *pay_cmd;
+  const struct GNUNET_CRYPTO_EddsaPublicKey *merchant_pub;
+
+  pas->pao = NULL;
+  if (pas->http_status != http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                http_status,
+                ec,
+                TALER_TESTING_interpreter_get_current_label
+                  (pas->is));
+    TALER_TESTING_interpreter_fail (pas->is);
+    return;
+  }
+
+  if ( NULL ==
+     ( pay_cmd = TALER_TESTING_interpreter_lookup_command
+       (pas->is, pas->pay_reference)))
+    TALER_TESTING_FAIL (pas->is);
+
+  if (MHD_HTTP_OK == http_status)
+  {
+    struct PaymentResponsePS mr;
+    /* Check signature */
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_fixed_auto ("sig",
+                                  &sig),
+      GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+                                  &mr.h_contract_terms),
+      GNUNET_JSON_spec_end ()
+    };
+
+    GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (obj,
+                                                   spec,
+                                                   &error_name,
+                                                   &error_line));
+    mr.purpose.purpose = htonl
+      (TALER_SIGNATURE_MERCHANT_PAYMENT_OK);
+    mr.purpose.size = htonl (sizeof (mr));
+
+    if (GNUNET_OK != TALER_TESTING_get_trait_peer_key_pub
+        (pay_cmd, 0, &merchant_pub))
+      TALER_TESTING_FAIL (pas->is);
+
+    if (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify
+      (TALER_SIGNATURE_MERCHANT_PAYMENT_OK,
+       &mr.purpose,
+       &sig,
+       merchant_pub))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Merchant signature given in"
+                  " response to /pay invalid\n");
+      TALER_TESTING_FAIL (pas->is);
+    }
+  }
+
+  TALER_TESTING_interpreter_next (pas->is);
+}
+
+/**
+ * Runs the command.  Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks.  Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @param is interpreter state
+ */
+static void
+pay_again_run (void *cls,
+               const struct TALER_TESTING_Command *cmd,
+               struct TALER_TESTING_Interpreter *is)
+{
+  struct PayAgainState *pas = cls;
+  const struct TALER_TESTING_Command *pay_cmd;
+
+  const char *proposal_reference;
+  const char *amount_with_fee;
+  const char *amount_without_fee;
+  
+  pas->is = is;
+  pay_cmd = TALER_TESTING_interpreter_lookup_command
+    (is, pas->pay_reference);
+  if (NULL == pay_cmd)
+    TALER_TESTING_FAIL (is);
+  
+  if (GNUNET_OK != TALER_TESTING_get_trait_proposal_reference
+      (pay_cmd, 0, &proposal_reference))
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_amount
+    (pay_cmd, AMOUNT_WITH_FEE, &amount_with_fee))
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_amount
+    (pay_cmd, AMOUNT_WITHOUT_FEE, &amount_without_fee))
+    TALER_TESTING_FAIL (is);
+
+  if ( NULL ==
+     ( pas->pao = _pay_run (pas->merchant_url,
+                            pas->ctx,
+                            pas->coin_reference,
+                            proposal_reference,
+                            is,
+                            amount_with_fee,
+                            amount_without_fee,
+                            pas->refund_fee,
+                            &TALER_MERCHANT_pay_wallet,
+                            &pay_again_cb,
+                            pas)) )
+    TALER_TESTING_FAIL (is);
+}
+
+/**
+ * Clean up after the command.  Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+pay_again_cleanup (void *cls,
+                   const struct TALER_TESTING_Command *cmd)
+{
+  struct PayAgainState *pas = cls;
+
+  if (NULL != pas->pao)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Command `%s' did not complete.\n",
+                TALER_TESTING_interpreter_get_current_label (
+                  pas->is));
+    TALER_MERCHANT_pay_cancel (pas->pao);
+  }
+
+  GNUNET_free (pas);
+}
+
+/**
+ * Make a "pay again" test command.
+ *
+ * @param label command label
+ * @param merchant_url merchant base URL
+ * @param pay_reference reference to the payment to replay
+ * @param coin_reference reference to the coins to use
+ * @param ctx main CURL context
+ * @param http_status expected HTTP response code
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay_again (const char *label,
+                             const char *merchant_url,
+                             const char *pay_reference,
+                             const char *coin_reference,
+                             const char *refund_fee,
+                             struct GNUNET_CURL_Context *ctx,
+                             unsigned int http_status)
+{
+
+  struct PayAgainState *pas;
+  struct TALER_TESTING_Command cmd;
+
+  pas = GNUNET_new (struct PayAgainState);
+  pas->http_status = http_status;
+  pas->pay_reference = pay_reference;
+  pas->coin_reference = coin_reference;
+  pas->ctx = ctx;
+  pas->merchant_url = merchant_url;
+  pas->refund_fee = refund_fee;
+
+  cmd.cls = pas;
+  cmd.label = label;
+  cmd.run = &pay_again_run;
+  cmd.cleanup = &pay_again_cleanup;
+  
+  return cmd;
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of
+ * submitting a refund request to an exchange.
+ *
+ * @param cls closure
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200) for
+ *        successful deposit; 0 if the exchange's reply is bogus
+ *        (fails to follow the protocol)
+ * @param ec taler-specific error code, #TALER_EC_NONE on success
+ * @param sign_key exchange key used to sign @a obj, or NULL
+ * @param obj the received JSON reply, should be kept as proof
+ *        (and, in particular, be forwarded to the customer)
+ */
+static void
+abort_refund_cb (void *cls,
+                unsigned int http_status,
+                enum TALER_ErrorCode ec,
+                const struct TALER_ExchangePublicKeyP *sign_key,
+                const json_t *obj)
+{
+  struct PayAbortRefundState *pars = cls;
+
+  pars->rh = NULL;
+  if (pars->http_status != http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                http_status,
+                ec,
+                TALER_TESTING_interpreter_get_current_label
+                  (pars->is));
+    TALER_TESTING_interpreter_fail (pars->is);
+    return;
+  }
+  TALER_TESTING_interpreter_next (pars->is);
+}
+
+/**
+ * FIXME.
+ */
+static void
+pay_abort_refund_cleanup (void *cls,
+                          const struct TALER_TESTING_Command *cmd)
+{
+  struct PayAbortRefundState *pars = cls;
+
+  if (NULL != pars->rh)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Command `%s' did not complete.\n",
+                TALER_TESTING_interpreter_get_current_label (
+                  pars->is));
+    TALER_EXCHANGE_refund_cancel (pars->rh);
+  }
+  GNUNET_free (pars);
+}
+
+/**
+ * FIXME.
+ */
+static void
+pay_abort_refund_run (void *cls,
+                      const struct TALER_TESTING_Command *cmd,
+                      struct TALER_TESTING_Interpreter *is)
+{
+  struct PayAbortRefundState *pars = cls;
+  struct TALER_Amount refund_fee;
+  struct TALER_Amount refund_amount;
+  const struct TALER_MERCHANT_RefundEntry *refund_entry;
+  unsigned int *num_refunds;
+  const struct TALER_TESTING_Command *abort_cmd;
+  const struct GNUNET_CRYPTO_EddsaPublicKey *merchant_pub;
+  const struct GNUNET_HashCode *h_contract_terms;
+
+  pars->is = is;
+
+  if ( NULL ==
+     ( abort_cmd = TALER_TESTING_interpreter_lookup_command
+       (is, pars->abort_reference)) )
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_uint
+      (abort_cmd, 0, &num_refunds))
+    TALER_TESTING_FAIL (is);
+
+  if (pars->num_coins >= *num_refunds)
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_h_contract_terms
+      (abort_cmd, 0, &h_contract_terms))
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_peer_key_pub
+      (abort_cmd, 0, &merchant_pub))
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_refund_entry
+      (abort_cmd, 0, &refund_entry))
+    TALER_TESTING_FAIL (is);
+
+  GNUNET_assert (GNUNET_OK == TALER_string_to_amount 
+    (pars->refund_amount, &refund_amount));
+  GNUNET_assert (GNUNET_OK == TALER_string_to_amount 
+    (pars->refund_fee, &refund_fee));
+
+  pars->rh = TALER_EXCHANGE_refund2
+    (pars->exchange,
+     &refund_amount,
+     &refund_fee,
+     h_contract_terms,
+     &refund_entry->coin_pub,
+     refund_entry->rtransaction_id,
+     (const struct TALER_MerchantPublicKeyP *) merchant_pub,
+     &refund_entry->merchant_sig,
+     &abort_refund_cb,
+     pars);
+
+  GNUNET_assert (NULL != pars->rh);
+}
+
+
+/**
+ * FIXME.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_pay_abort_refund
+  (const char *label,
+   struct TALER_EXCHANGE_Handle *exchange,
+   const char *abort_reference,
+   unsigned int num_coins,
+   const char *refund_amount,
+   const char *refund_fee,
+   unsigned int http_status)
+{
+  struct PayAbortRefundState *pars;
+  struct TALER_TESTING_Command cmd;
+
+  pars = GNUNET_new (struct PayAbortRefundState);
+  pars->abort_reference = abort_reference;
+  pars->num_coins = num_coins;
+  pars->refund_amount = refund_amount;
+  pars->refund_fee = refund_fee;
+  pars->http_status = http_status;
+  pars->exchange = exchange;
+
+  cmd.cls = pars;
+  cmd.label = label;
+  cmd.run = &pay_abort_refund_run;
+  cmd.cleanup = &pay_abort_refund_cleanup;
+
+  return cmd;
+}
+
+/* end of testing_api_cmd_pay.c */
diff --git a/src/lib/testing_api_cmd_proposal.c 
b/src/lib/testing_api_cmd_proposal.c
new file mode 100644
index 0000000..3c6c439
--- /dev/null
+++ b/src/lib/testing_api_cmd_proposal.c
@@ -0,0 +1,560 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 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 exchange/testing_api_cmd_exec_merchant.c
+ * @brief command to execute the merchant backend service.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+struct ProposalState
+{
+
+  /**
+   * The order.
+   */
+  const char *order;
+
+  /**
+   * Expected status code.
+   */
+  unsigned int http_status;
+
+  /**
+   * Order id.
+   */
+  const char *order_id;
+
+  /**
+   * Contract terms obtained from the backend.
+   */
+  const char *contract_terms;
+
+  /**
+   * Proposal data hash code.  Recall: proposal data is the part
+   * of the contract terms without the signature of the merchant.
+   */
+  struct GNUNET_HashCode h_contract_terms;
+
+  /**
+   * The /proposal operation handle.
+   */
+  struct TALER_MERCHANT_ProposalOperation *po;
+
+  /**
+   * The (initial) /proposal/lookup operation handle.
+   */
+  struct TALER_MERCHANT_ProposalLookupOperation *plo;
+
+  /**
+   * The nonce.
+   */
+  struct GNUNET_CRYPTO_EddsaPublicKey nonce;
+
+  /**
+   * The merchant instance.
+   */
+  const char *instance;
+
+  /**
+   * URL of the merchant backend.
+   */
+  const char *merchant_url;
+
+  /**
+   * The curl context; used to be fed to the merchant lib.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Merchant signature over the proposal.
+   */
+  struct TALER_MerchantSignatureP merchant_sig;
+
+  /**
+   * Merchant public key.
+   */
+  struct TALER_MerchantPublicKeyP merchant_pub;
+};
+
+struct ProposalLookupState
+{
+  /**
+   * The interpreter state.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * URL of the merchant backend.
+   */
+  const char *merchant_url;
+
+  /**
+   * The curl context; used to be fed to the merchant lib.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+  /**
+   * Expected status code.
+   */
+  unsigned int http_status;
+
+  /**
+   * The (initial) /proposal/lookup operation handle.
+   */
+  struct TALER_MERCHANT_ProposalLookupOperation *plo;
+
+  /**
+   * Reference to a proposal operation.
+   */
+  const char *proposal_reference;
+};
+
+/**
+ * Extract information from a command that is useful for other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param selector more detailed information about which object
+ *                 to return in case there were multiple generated
+ *                 by the command
+ * @return #GNUNET_OK on success
+ */
+static int
+proposal_traits (void *cls,
+                 void **ret,
+                 const char *trait,
+                 unsigned int index)
+{
+
+  struct ProposalState *ps = cls;
+  #define MAKE_TRAIT_NONCE(ptr) \
+    TALER_TESTING_make_trait_peer_key_pub (1, ptr)
+
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_order_id (0, ps->order_id),
+    TALER_TESTING_make_trait_contract_terms
+      (0, ps->contract_terms),
+    TALER_TESTING_make_trait_h_contract_terms
+      (0, &ps->h_contract_terms),
+    TALER_TESTING_make_trait_merchant_sig (0, &ps->merchant_sig),
+    TALER_TESTING_make_trait_peer_key_pub
+      (0, &ps->merchant_pub.eddsa_pub),
+    MAKE_TRAIT_NONCE (&ps->nonce),
+    TALER_TESTING_trait_end ()
+  };
+  
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+  return GNUNET_SYSERR;
+}
+
+
+/**
+ * Used to initialize the proposal after it was created.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code we got
+ * @param json full response we got
+ */
+static void
+proposal_lookup_initial_cb
+  (void *cls,
+   unsigned int http_status,
+   const json_t *json,
+   const json_t *contract_terms,
+   const struct TALER_MerchantSignatureP *sig,
+   const struct GNUNET_HashCode *hash)
+{
+  struct ProposalState *ps = cls;
+  struct TALER_MerchantPublicKeyP merchant_pub;
+  const char *error_name;
+  unsigned int error_line;
+
+  ps->plo = NULL;
+  if (ps->http_status != http_status)
+    TALER_TESTING_FAIL (ps->is);
+
+  ps->contract_terms = json_dumps (contract_terms,
+                                   JSON_COMPACT);
+  ps->h_contract_terms = *hash;
+  ps->merchant_sig = *sig;
+
+
+  struct GNUNET_JSON_Specification spec[] = {
+    GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+                                 &merchant_pub),
+    GNUNET_JSON_spec_end()
+  };
+
+  if (GNUNET_OK !=
+      GNUNET_JSON_parse (contract_terms,
+                         spec,
+                         &error_name,
+                         &error_line))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Parser failed on %s:%u\n",
+                error_name,
+                error_line);
+    fprintf (stderr, "%s\n", ps->contract_terms);
+    TALER_TESTING_FAIL (ps->is);
+  }
+
+  ps->merchant_pub = merchant_pub;
+
+  TALER_TESTING_interpreter_next (ps->is);
+}
+
+
+/**
+ * Callback that processes the response following a
+ * proposal's put.  NOTE: no contract terms are included
+ * here; they need to be taken via the "proposal lookup"
+ * method.
+ *
+ * @param cls closure.
+ * @param http_status HTTP response code coming from
+ *        the backend.
+ * @param ec error code.
+ * @param obj when successful, it matches the format:
+ *        '{"order_id": "<order_id>"}'
+ */
+
+static void
+proposal_cb (void *cls,
+             unsigned int http_status,
+            enum TALER_ErrorCode ec,
+             const json_t *obj,
+             const char *order_id)
+{
+  struct ProposalState *ps = cls;
+
+  ps->po = NULL;
+  switch (http_status)
+  {
+  case MHD_HTTP_OK:
+    ps->order_id = GNUNET_strdup (order_id);
+    break;
+  default:
+  {
+    char *s = json_dumps (obj, JSON_COMPACT);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected status code from /proposal:" \
+                " %u (%d). Command %s, response: %s\n",
+                http_status,
+                ec,
+                TALER_TESTING_interpreter_get_current_label (
+                  ps->is),
+                s);
+    GNUNET_free_non_null (s);
+    TALER_TESTING_interpreter_fail (ps->is);
+  }
+  return;
+  }
+
+
+  if (NULL ==
+     (ps->plo = TALER_MERCHANT_proposal_lookup
+       (ps->ctx,
+        ps->merchant_url,
+        ps->order_id,
+        "default",
+        &ps->nonce,
+        &proposal_lookup_initial_cb,
+        ps)))
+    TALER_TESTING_FAIL (ps->is);
+}
+
+
+/**
+ * Runs the command.  Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks.  Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @param is interpreter state
+ */
+static void
+proposal_run (void *cls,
+              const struct TALER_TESTING_Command *cmd,
+              struct TALER_TESTING_Interpreter *is)
+{
+  struct ProposalState *ps = cls;
+  json_t *order;
+  json_error_t error;
+
+  ps->is = is;
+  order = json_loads (ps->order,
+                      JSON_REJECT_DUPLICATES,
+                      &error);
+  if (NULL == order)
+  {
+    // human error here.
+    GNUNET_break (0);
+    fprintf (stderr, "%s\n", error.text);
+    TALER_TESTING_interpreter_fail (is); 
+    return;
+  }
+
+  GNUNET_CRYPTO_random_block
+    (GNUNET_CRYPTO_QUALITY_WEAK,
+     &ps->nonce,
+     sizeof (struct GNUNET_CRYPTO_EddsaPublicKey));
+  if (NULL != ps->instance)
+  {
+    json_t *merchant;
+    merchant = json_object ();
+    json_object_set_new (merchant,
+                         "instance",
+                         json_string (ps->instance));
+    json_object_set_new (order,
+                         "merchant",
+                         merchant);
+  }
+
+  ps->po = TALER_MERCHANT_order_put (ps->ctx,
+                                     ps->merchant_url,
+                                     order,
+                                     &proposal_cb,
+                                     ps);
+  json_decref (order);
+  GNUNET_assert (NULL != ps->po);
+}
+
+/**
+ * Clean up after the command.  Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+proposal_cleanup (void *cls,
+                  const struct TALER_TESTING_Command *cmd)
+{
+  struct ProposalState *ps = cls;
+
+  if (NULL != ps->po)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Command '%s' did not complete (proposal put)\n",
+                cmd->label);
+    TALER_MERCHANT_proposal_cancel (ps->po);
+    ps->po = NULL;
+  }
+
+  if (NULL != ps->plo)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Command '%s' did not complete"
+                " (proposal lookup)\n",
+                cmd->label);
+    TALER_MERCHANT_proposal_lookup_cancel (ps->plo);
+    ps->plo = NULL;
+  }
+
+  GNUNET_free ((void *) ps->order_id);
+  GNUNET_free ((void *) ps->contract_terms);
+  GNUNET_free (ps);
+}
+
+/**
+ * Clean up after the command.  Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+proposal_lookup_cleanup (void *cls,
+                         const struct TALER_TESTING_Command *cmd)
+{
+  struct ProposalLookupState *pls = cls;
+
+  if (NULL != pls->plo)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Command '%s' did not complete\n",
+                cmd->label);
+    TALER_MERCHANT_proposal_lookup_cancel (pls->plo);
+  }
+  GNUNET_free (pls);
+}
+
+
+/**
+ * Make the /proposal command.
+ *
+ * @param label command label
+ * @param merchant_reference label to the merchant command.  Used
+ *        to get its base url.
+ * @param ctx context
+ * @param http_status HTTP status code.
+ * @param order the order
+ * @param instance the merchant instance
+ * @param merchant_url the merchant backend (base) url
+ *
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proposal (const char *label,
+                            const char *merchant_url,
+                            struct GNUNET_CURL_Context *ctx,
+                            unsigned int http_status,
+                            const char *order,
+                            const char *instance)
+{
+  struct TALER_TESTING_Command cmd;
+  struct ProposalState *ps;
+
+  ps = GNUNET_new (struct ProposalState);
+  ps->order = order;
+  ps->http_status = http_status;
+  ps->ctx = ctx;
+  ps->merchant_url = merchant_url;
+
+  cmd.cls = ps;
+  cmd.label = label;
+  cmd.run = &proposal_run;
+  cmd.cleanup = &proposal_cleanup;
+  cmd.traits = &proposal_traits;
+  return cmd;
+}
+
+/**
+ * Callback for GET /proposal issued at backend. Just check
+ * whether response code is as expected.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code we got
+ * @param json full response we got
+ */
+static void
+proposal_lookup_cb (void *cls,
+                    unsigned int http_status,
+                    const json_t *json,
+                    const json_t *contract_terms,
+                    const struct TALER_MerchantSignatureP *sig,
+                    const struct GNUNET_HashCode *hash)
+{
+  struct ProposalLookupState *pls = cls;
+
+  pls->plo = NULL;
+  if (pls->http_status != http_status)
+    TALER_TESTING_FAIL (pls->is);
+
+  TALER_TESTING_interpreter_next (pls->is);
+}
+
+
+/**
+ * Runs the command.  Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks.  Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @param is interpreter state
+ */
+static void
+proposal_lookup_run (void *cls,
+                     const struct TALER_TESTING_Command *cmd,
+                     struct TALER_TESTING_Interpreter *is)
+{
+  struct ProposalLookupState *pls = cls;
+  const struct TALER_TESTING_Command *proposal_cmd;
+  const char *order_id;
+  const struct GNUNET_CRYPTO_EddsaPublicKey *nonce;
+  #define GET_TRAIT_NONCE(cmd,ptr) \
+    TALER_TESTING_get_trait_peer_key_pub (cmd, 1, ptr)
+
+  pls->is = is;
+  proposal_cmd = TALER_TESTING_interpreter_lookup_command
+    (is, pls->proposal_reference);
+
+  if (NULL == proposal_cmd)
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != GET_TRAIT_NONCE (proposal_cmd,
+                                    &nonce))
+    TALER_TESTING_FAIL (is); 
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_order_id
+      (proposal_cmd, 0, &order_id))
+    TALER_TESTING_FAIL (is);
+
+  pls->plo = TALER_MERCHANT_proposal_lookup (pls->ctx,
+                                             pls->merchant_url,
+                                             order_id,
+                                             "default",
+                                             nonce,
+                                             &proposal_lookup_cb,
+                                             pls);
+  GNUNET_assert (NULL != pls->plo);
+}
+
+
+
+/**
+ * Make a "proposal lookup" command.
+ *
+ * @param label command label
+ * @param 
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_proposal_lookup
+  (const char *label,
+   struct GNUNET_CURL_Context *ctx,
+   const char *merchant_url,
+   unsigned int http_status,
+   const char *proposal_reference)
+{
+  struct ProposalLookupState *pls;
+  struct TALER_TESTING_Command cmd;
+  
+  pls = GNUNET_new (struct ProposalLookupState);
+  pls->http_status = http_status;
+  pls->proposal_reference = proposal_reference;
+  pls->merchant_url = merchant_url;
+  pls->ctx = ctx;
+
+  cmd.cls = pls;
+  cmd.label = label;
+  cmd.run = &proposal_lookup_run;
+  cmd.cleanup = &proposal_lookup_cleanup;
+
+  return cmd;
+}
diff --git a/src/lib/testing_api_cmd_refund.c b/src/lib/testing_api_cmd_refund.c
new file mode 100644
index 0000000..b85dcf6
--- /dev/null
+++ b/src/lib/testing_api_cmd_refund.c
@@ -0,0 +1,457 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 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 lib/testing_api_cmd_refund.c
+ * @brief command to test refunds.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+struct RefundIncreaseState
+{
+  struct TALER_MERCHANT_RefundIncreaseOperation *rio;
+
+  const char *merchant_url;
+
+  struct GNUNET_CURL_Context *ctx;
+
+  const char *order_id;
+
+  const char *refund_amount;
+
+  const char *refund_fee;
+
+  const char *reason;
+
+  struct TALER_TESTING_Interpreter *is;
+};
+
+struct RefundLookupState
+{
+  struct TALER_MERCHANT_RefundLookupOperation *rlo;
+
+  const char *merchant_url;
+
+  struct GNUNET_CURL_Context *ctx;
+
+  const char *order_id;
+
+  const char *pay_reference;
+
+  const char *increase_reference;
+
+  struct TALER_TESTING_Interpreter *is;
+};
+
+
+/**
+ * Clean up after the command.  Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+refund_increase_cleanup (void *cls,
+                         const struct TALER_TESTING_Command *cmd)
+{
+  struct RefundIncreaseState *ris = cls;
+
+  if (NULL != ris->rio)
+  {
+    TALER_LOG_WARNING ("Refund-increase operation"
+                       " did not complete\n");
+    TALER_MERCHANT_refund_increase_cancel (ris->rio);
+  }
+  GNUNET_free (ris);
+}
+
+/**
+ * Clean up after the command.  Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+refund_lookup_cleanup (void *cls,
+                       const struct TALER_TESTING_Command *cmd)
+{
+  /* FIXME: make sure no other data must be free'd */
+  struct RefundLookupState *rls = cls;
+
+  if (NULL != rls->rlo)
+  {
+    TALER_LOG_WARNING ("Refund-lookup operation"
+                       " did not complete\n");
+    TALER_MERCHANT_refund_lookup_cancel (rls->rlo);
+  }
+  GNUNET_free (rls);
+}
+
+/**
+ * Process POST /refund (increase) response
+ *
+ * @param cls closure
+ * @param http_status HTTP status code
+ * @param ec taler-specific error object
+ * @param obj response body; is NULL on success.
+ */
+static void
+refund_increase_cb (void *cls,
+                    unsigned int http_status,
+                    enum TALER_ErrorCode ec,
+                    const json_t *obj)
+{
+  struct RefundIncreaseState *ris = cls;
+
+  ris->rio = NULL;
+  if (MHD_HTTP_OK != http_status)
+    TALER_TESTING_FAIL (ris->is);
+
+  TALER_TESTING_interpreter_next (ris->is);
+}
+
+static void
+refund_increase_run (void *cls,
+                     const struct TALER_TESTING_Command *cmd,
+                     struct TALER_TESTING_Interpreter *is)
+{
+  struct RefundIncreaseState *ris = cls;
+  struct TALER_Amount refund_amount;
+
+  ris->is = is;
+  if (GNUNET_OK != TALER_string_to_amount (ris->refund_amount,
+                                           &refund_amount))
+    TALER_TESTING_FAIL (is);
+  ris->rio = TALER_MERCHANT_refund_increase (ris->ctx,
+                                             ris->merchant_url,
+                                             ris->order_id,
+                                             &refund_amount,
+                                             ris->reason,
+                                             "default",
+                                             &refund_increase_cb,
+                                             ris);
+  GNUNET_assert (NULL != ris->rio);
+}
+
+/**
+ * Callback that frees all the elements in the hashmap
+ *
+ * @param cls closure, NULL
+ * @param key current key
+ * @param value a `struct TALER_Amount`
+ * @return always #GNUNET_YES (continue to iterate)
+ */
+static int
+hashmap_free (void *cls,
+              const struct GNUNET_HashCode *key,
+              void *value)
+{
+  struct TALER_Amount *refund_amount = value;
+
+  GNUNET_free (refund_amount);
+  return GNUNET_YES;
+}
+
+
+/**
+ * Process GET /refund (increase) response.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code
+ * @param ec taler-specific error object
+ * @param obj response body; is NULL on error.
+ */
+static void
+refund_lookup_cb (void *cls,
+                  unsigned int http_status,
+                  enum TALER_ErrorCode ec,
+                  const json_t *obj)
+{
+  struct RefundLookupState *rls = cls;
+  struct GNUNET_CONTAINER_MultiHashMap *map;
+  size_t index;
+  json_t *elem;
+  const char *error_name;
+  unsigned int error_line;
+  struct GNUNET_HashCode h_coin_pub;
+  const char *coin_reference;
+  char *coin_reference_dup;
+  const char *icoin_reference;
+  const struct TALER_TESTING_Command *pay_cmd;
+  const struct TALER_TESTING_Command *icoin_cmd;
+  const struct TALER_TESTING_Command *increase_cmd;
+  const char *refund_amount;
+  struct TALER_Amount acc;
+  struct TALER_Amount ra;
+  const json_t *arr;
+
+  rls->rlo = NULL;
+  if (MHD_HTTP_OK != http_status)
+    TALER_TESTING_FAIL (rls->is);
+
+  map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
+  arr = json_object_get (obj, "refund_permissions");
+  if (NULL == arr)
+    TALER_TESTING_FAIL (rls->is);
+
+  json_array_foreach (arr, index, elem)
+  {
+    struct TALER_CoinSpendPublicKeyP coin_pub;
+    struct TALER_Amount *irefund_amount = GNUNET_new
+      (struct TALER_Amount);
+    struct GNUNET_JSON_Specification spec[] = {
+      GNUNET_JSON_spec_fixed_auto ("coin_pub", &coin_pub),
+      TALER_JSON_spec_amount ("refund_amount", irefund_amount),
+      GNUNET_JSON_spec_end ()
+    };
+
+    GNUNET_assert (GNUNET_OK == GNUNET_JSON_parse (elem,
+                                                  spec,
+                                                   &error_name,
+                                                   &error_line));
+    GNUNET_CRYPTO_hash (&coin_pub,
+                       sizeof (struct TALER_CoinSpendPublicKeyP),
+                       &h_coin_pub);
+    GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put
+      (map,
+       &h_coin_pub,
+       irefund_amount,
+       GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+  };
+
+  if ( NULL ==
+     ( pay_cmd = TALER_TESTING_interpreter_lookup_command
+       (rls->is, rls->pay_reference)))
+    TALER_TESTING_FAIL (rls->is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_coin_reference
+      (pay_cmd, 0, &coin_reference))
+    TALER_TESTING_FAIL (rls->is);
+
+  GNUNET_assert (GNUNET_OK == TALER_amount_get_zero ("EUR",
+                                                     &acc));
+  coin_reference_dup = GNUNET_strdup (coin_reference);
+  for (icoin_reference = strtok (coin_reference_dup, ";");
+       NULL != icoin_reference;
+       icoin_reference = strtok (NULL, ";"))
+  {
+    struct TALER_CoinSpendPrivateKeyP *icoin_priv;
+    struct TALER_CoinSpendPublicKeyP icoin_pub;
+    struct GNUNET_HashCode h_icoin_pub;
+    struct TALER_Amount *iamount;
+
+    if ( NULL ==
+       ( icoin_cmd = TALER_TESTING_interpreter_lookup_command
+         (rls->is, icoin_reference)))
+    {
+      GNUNET_break (0);
+      TALER_LOG_ERROR ("Bad reference `%s'\n",
+                       icoin_reference); 
+      TALER_TESTING_interpreter_fail (rls->is);
+      return;
+    }
+
+   if (GNUNET_OK != TALER_TESTING_get_trait_coin_priv
+      (icoin_cmd, 0, &icoin_priv))
+   {
+     GNUNET_break (0);
+     TALER_LOG_ERROR ("Command `%s' failed to give coin"
+                      " priv trait\n",
+                      icoin_reference); 
+     TALER_TESTING_interpreter_fail (rls->is);
+     return;
+    }
+     
+    GNUNET_CRYPTO_eddsa_key_get_public (&icoin_priv->eddsa_priv,
+                                        &icoin_pub.eddsa_pub);
+    GNUNET_CRYPTO_hash (&icoin_pub,
+                        sizeof (struct TALER_CoinSpendPublicKeyP),
+                        &h_icoin_pub);
+
+    iamount = GNUNET_CONTAINER_multihashmap_get
+      (map, &h_icoin_pub);
+
+    /* Can be NULL: not all coins are involved in refund */
+    if (NULL == iamount)
+      continue;
+
+    GNUNET_assert (GNUNET_OK == TALER_amount_add (&acc,
+                                                  &acc,
+                                                  iamount));
+  }
+
+  GNUNET_free (coin_reference_dup);
+  
+  if ( NULL ==
+     ( increase_cmd = TALER_TESTING_interpreter_lookup_command
+       (rls->is, rls->increase_reference)))
+    TALER_TESTING_FAIL (rls->is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_amount
+      (increase_cmd, 0, &refund_amount))
+    TALER_TESTING_FAIL (rls->is);
+
+  if (GNUNET_OK != TALER_string_to_amount (refund_amount,
+                                           &ra))
+    TALER_TESTING_FAIL (rls->is);
+
+  GNUNET_CONTAINER_multihashmap_iterate (map,
+                                         &hashmap_free,
+                                         NULL);
+  GNUNET_CONTAINER_multihashmap_destroy (map);
+
+  if (0 != TALER_amount_cmp (&acc,
+                             &ra))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Incomplete refund: expected '%s', got '%s'\n",
+                refund_amount,
+                TALER_amount_to_string (&acc));
+    TALER_TESTING_interpreter_fail (rls->is);
+    return;
+  }
+
+  TALER_TESTING_interpreter_next (rls->is);
+}
+
+
+static void
+refund_lookup_run (void *cls,
+                   const struct TALER_TESTING_Command *cmd,
+                   struct TALER_TESTING_Interpreter *is)
+{
+  struct RefundLookupState *rls = cls;
+  
+  rls->is = is;
+  rls->rlo = TALER_MERCHANT_refund_lookup (rls->ctx,
+                                           rls->merchant_url,
+                                           rls->order_id,
+                                           "default",
+                                           &refund_lookup_cb,
+                                           rls);
+  GNUNET_assert (NULL != rls->rlo);
+}
+
+
+/**
+ * Extract information from a command that is useful for other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param selector more detailed information about which object
+ *                 to return in case there were multiple generated
+ *                 by the command
+ * @return #GNUNET_OK on success
+ */
+static int
+refund_increase_traits (void *cls,
+                        void **ret,
+                        const char *trait,
+                        unsigned int index)
+{
+  struct RefundIncreaseState *ris = cls;
+  
+  struct TALER_TESTING_Trait traits[] = {
+    TALER_TESTING_make_trait_amount (0, ris->refund_amount),
+    TALER_TESTING_trait_end ()
+  };
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+
+  return GNUNET_SYSERR;
+}
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_increase
+  (const char *label,
+   const char *merchant_url,
+   struct GNUNET_CURL_Context *ctx,
+   const char *reason,
+   const char *order_id,
+   const char *refund_amount,
+   const char *refund_fee)
+{
+  struct RefundIncreaseState *ris;
+  struct TALER_TESTING_Command cmd;
+
+  ris = GNUNET_new (struct RefundIncreaseState);
+  ris->merchant_url = merchant_url;
+  ris->ctx = ctx;
+  ris->order_id = order_id;
+  ris->refund_amount = refund_amount;
+  ris->refund_fee = refund_fee;
+  ris->reason = reason;
+
+  cmd.cls = ris;
+  cmd.label = label;
+  cmd.run = &refund_increase_run;
+  cmd.cleanup = &refund_increase_cleanup;
+  cmd.traits = &refund_increase_traits;
+
+  return cmd;
+}
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_refund_lookup
+  (const char *label,
+   const char *merchant_url,
+   struct GNUNET_CURL_Context *ctx,
+   const char *increase_reference,
+   const char *pay_reference,
+   const char *order_id)
+{
+  struct RefundLookupState *rls;
+  struct TALER_TESTING_Command cmd;
+
+  rls = GNUNET_new (struct RefundLookupState);
+  rls->merchant_url = merchant_url;
+  rls->ctx = ctx;
+  rls->order_id = order_id;
+  rls->pay_reference = pay_reference;
+  rls->increase_reference = increase_reference;
+
+  cmd.cls = rls;
+  cmd.label = label;
+  cmd.run = &refund_lookup_run;
+  cmd.cleanup = &refund_lookup_cleanup;
+
+  return cmd;
+}
+
+
+
+/* end of testing_api_cmd_refund.c */
diff --git a/src/lib/testing_api_cmd_tip.c b/src/lib/testing_api_cmd_tip.c
new file mode 100644
index 0000000..111180a
--- /dev/null
+++ b/src/lib/testing_api_cmd_tip.c
@@ -0,0 +1,777 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 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 lib/testing_api_cmd_tip.c
+ * @brief command to test the tipping.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+
+struct TipPickupState
+{
+  const char *merchant_url;
+
+  struct GNUNET_CURL_Context *ctx;
+
+  unsigned int http_status;
+
+  const char *authorize_reference;
+
+  /**
+   * Set to non-NULL to a label of another pick up operation
+   * that we should replay.
+   */
+  const char *replay_reference;
+
+  struct TALER_MERCHANT_TipPickupOperation *tpo;
+
+  struct TALER_TESTING_Interpreter *is;
+
+  const char **amounts;
+
+  unsigned int num_coins;
+  
+  const struct TALER_EXCHANGE_DenomPublicKey **dks;
+
+  struct TALER_PlanchetSecretsP *psa;
+
+  /**
+   * Temporary data structure of @e num_coins entries for the
+   * withdraw operations.
+   */
+  struct WithdrawHandle *withdraws;
+
+  /**
+   * Set (by the interpreter) to an array of @a num_coins
+   * signatures created from the (successful) tip operation.
+   */
+  struct TALER_DenominationSignature *sigs;
+
+  enum TALER_ErrorCode expected_ec;
+
+  struct TALER_EXCHANGE_Handle *exchange;
+};
+
+
+struct TipQueryState
+{
+  const char *merchant_url;
+
+  struct GNUNET_CURL_Context *ctx;
+
+  unsigned int http_status;
+
+  const char *instance;
+
+  struct TALER_MERCHANT_TipQueryOperation *tqo;
+
+  struct TALER_TESTING_Interpreter *is;
+
+  const char *expected_amount_picked_up;
+
+  const char *expected_amount_authorized;
+
+  const char *expected_amount_available;
+};
+
+
+struct TipAuthorizeState
+{
+  const char *merchant_url;
+
+  struct GNUNET_CURL_Context *ctx;
+
+  unsigned int http_status;
+
+  const char *instance;
+
+  const char *justification;
+
+  const char *amount;
+
+  enum TALER_ErrorCode expected_ec;
+
+  const char *exchange_url;
+
+  struct GNUNET_HashCode tip_id;
+
+  struct GNUNET_TIME_Absolute tip_expiration;
+
+  struct TALER_MERCHANT_TipAuthorizeOperation *tao;
+
+  struct TALER_TESTING_Interpreter *is;
+};
+
+/**
+ * Callback for a /tip-authorize request.  Returns the result
+ * of the operation.
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant backend
+ * @param ec taler-specific error code
+ * @param tip_id which tip ID should be used to pickup the tip
+ * @param tip_expiration when does the tip expire (needs to be
+ *        picked up before this time)
+ * @param exchange_url at what exchange can the tip be picked up
+ */
+static void
+tip_authorize_cb (void *cls,
+                  unsigned int http_status,
+                  enum TALER_ErrorCode ec,
+                  const struct GNUNET_HashCode *tip_id,
+                  struct GNUNET_TIME_Absolute tip_expiration,
+                  const char *exchange_url)
+{
+  struct TipAuthorizeState *tas = cls; 
+
+  tas->tao = NULL;
+  if (tas->http_status != http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d)"
+                " to command %s\n",
+                http_status,
+                ec,
+                TALER_TESTING_interpreter_get_current_label
+                  (tas->is));
+
+    TALER_TESTING_interpreter_fail (tas->is);
+    return;
+  }
+
+  if (tas->expected_ec != ec)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected error code %d (%u) to command %s\n",
+                ec,
+                http_status,
+                TALER_TESTING_interpreter_get_current_label
+                  (tas->is));
+    TALER_TESTING_interpreter_fail (tas->is);
+    return;
+  }
+  if ( (MHD_HTTP_OK == http_status) &&
+       (TALER_EC_NONE == ec) )
+  {
+    if (0 != strcmp (exchange_url,
+                     tas->exchange_url))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                  "Unexpected exchange URL %s to command %s\n",
+                  exchange_url,
+                  TALER_TESTING_interpreter_get_current_label
+                    (tas->is));
+      TALER_TESTING_interpreter_fail (tas->is);
+      return;
+    }
+    tas->tip_id = *tip_id;
+    tas->tip_expiration = tip_expiration;
+  }
+
+  TALER_TESTING_interpreter_next (tas->is);
+}
+
+
+/**
+ * Runs the command.  Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks.  Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @param is interpreter state
+ */
+static void
+tip_authorize_run (void *cls,
+                   const struct TALER_TESTING_Command *cmd,
+                   struct TALER_TESTING_Interpreter *is)
+{
+  struct TipAuthorizeState *tas = cls;
+  struct TALER_Amount amount; 
+
+  tas->is = is;
+  if (GNUNET_OK != TALER_string_to_amount (tas->amount,
+                                           &amount))
+    TALER_TESTING_FAIL (is);
+
+  tas->tao = TALER_MERCHANT_tip_authorize
+    (tas->ctx,
+     tas->merchant_url,
+     "http://merchant.com/pickup";,
+     "http://merchant.com/continue";,
+     &amount,
+     tas->instance,
+     tas->justification,
+     tip_authorize_cb,
+     tas);
+
+  GNUNET_assert (NULL != tas->tao);
+}
+
+
+/**
+ * FIXME
+ */
+static void
+tip_authorize_cleanup (void *cls,
+                       const struct TALER_TESTING_Command *cmd)
+{
+  struct TipAuthorizeState *tas = cls;
+
+  if (NULL != tas->tao)
+  {
+    TALER_LOG_WARNING ("Tip-autorize operation"
+                       " did not complete\n");
+    TALER_MERCHANT_tip_authorize_cancel (tas->tao);
+  }
+  GNUNET_free (tas);
+}
+
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_authorize (const char *label,
+                                 const char *merchant_url,
+                                 struct GNUNET_CURL_Context *ctx,
+                                 unsigned int http_status,
+                                 const char *instance,
+                                 const char *justification,
+                                 const char *amount)
+{
+  struct TipAuthorizeState *tas;
+  struct TALER_TESTING_Command cmd;
+
+  tas = GNUNET_new (struct TipAuthorizeState);
+  tas->merchant_url = merchant_url;
+  tas->ctx = ctx;
+  tas->instance = instance;
+  tas->justification = justification;
+  tas->amount = amount;
+  tas->http_status = http_status;
+
+  cmd.label = label;
+  cmd.cls = tas;
+  cmd.run = &tip_authorize_run;
+  cmd.cleanup = &tip_authorize_cleanup;
+  
+  return cmd;
+}
+
+/**
+ * Callback to process a GET /tip-query request
+ *
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param ec Taler-specific error code
+ * @param raw raw response body
+ */
+static void
+tip_query_cb (void *cls,
+              unsigned int http_status,
+              enum TALER_ErrorCode ec,
+              const json_t *raw,
+              struct GNUNET_TIME_Absolute reserve_expiration,
+              struct TALER_ReservePublicKeyP *reserve_pub,
+              struct TALER_Amount *amount_authorized,
+              struct TALER_Amount *amount_available,
+              struct TALER_Amount *amount_picked_up)
+{
+  struct TipQueryState *tqs = cls;
+  struct TALER_Amount a;
+
+  tqs->tqo = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+              "Tip query callback at command `%s'\n",
+              TALER_TESTING_interpreter_get_current_label
+                (tqs->is));
+
+  GNUNET_assert (NULL != reserve_pub);
+  GNUNET_assert (NULL != amount_authorized);
+  GNUNET_assert (NULL != amount_available);
+  GNUNET_assert (NULL != amount_picked_up);
+
+  if (tqs->expected_amount_available)
+  {
+    GNUNET_assert (GNUNET_OK == TALER_string_to_amount
+      (tqs->expected_amount_available, &a));
+    TALER_LOG_INFO ("expected available %s, actual %s\n",
+                    TALER_amount_to_string (&a),
+                    TALER_amount_to_string (amount_available));
+    if (0 != TALER_amount_cmp (amount_available, &a))
+      TALER_TESTING_FAIL (tqs->is);
+  }
+
+  if (tqs->expected_amount_authorized)
+  {
+    GNUNET_assert (GNUNET_OK == TALER_string_to_amount
+      (tqs->expected_amount_authorized, &a));
+    TALER_LOG_INFO ("expected authorized %s, actual %s\n",
+                    TALER_amount_to_string (&a),
+                    TALER_amount_to_string (amount_authorized));
+    if (0 != TALER_amount_cmp (amount_authorized, &a))
+      TALER_TESTING_FAIL (tqs->is);
+  }
+
+  if (tqs->expected_amount_picked_up)
+  {
+    GNUNET_assert (GNUNET_OK == TALER_string_to_amount
+      (tqs->expected_amount_picked_up, &a));
+    TALER_LOG_INFO ("expected picked_up %s, actual %s\n",
+                    TALER_amount_to_string (&a),
+                    TALER_amount_to_string (amount_picked_up));
+    if (0 != TALER_amount_cmp (amount_picked_up, &a))
+      TALER_TESTING_FAIL (tqs->is);
+  }
+
+  if (tqs->http_status != http_status)
+    TALER_TESTING_FAIL (tqs->is);
+
+  TALER_TESTING_interpreter_next (tqs->is);
+}
+
+/**
+ * FIXME
+ */
+static void
+tip_query_cleanup (void *cls,
+                   const struct TALER_TESTING_Command *cmd)
+{
+  struct TipQueryState *tqs = cls;
+
+  if (NULL != tqs->tqo)
+  {
+    TALER_LOG_WARNING ("Tip-query operation"
+                       " did not complete\n");
+    TALER_MERCHANT_tip_query_cancel (tqs->tqo);
+  }
+  GNUNET_free (tqs);
+}
+
+/**
+ * FIXME
+ */
+static void
+tip_query_run (void *cls,
+               const struct TALER_TESTING_Command *cmd,
+               struct TALER_TESTING_Interpreter *is)
+{
+  struct TipQueryState *tqs = cls;
+  
+  tqs->is = is;
+  tqs->tqo = TALER_MERCHANT_tip_query (tqs->ctx,
+                                       tqs->merchant_url,
+                                       tqs->instance,
+                                       &tip_query_cb,
+                                       tqs);
+  GNUNET_assert (NULL != tqs->tqo);
+}
+
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_query (const char *label,
+                             const char *merchant_url,
+                             struct GNUNET_CURL_Context *ctx,
+                             unsigned int http_status,
+                             const char *instance)
+{
+  struct TipQueryState *tqs;
+  struct TALER_TESTING_Command cmd;
+
+  tqs = GNUNET_new (struct TipQueryState);
+  tqs->merchant_url = merchant_url;
+  tqs->ctx = ctx;
+  tqs->instance = instance;
+
+  cmd.cls = tqs;
+  cmd.label = label;
+  cmd.run = &tip_query_run;
+  cmd.cleanup = &tip_query_cleanup;
+  
+  return cmd;
+}
+
+/**
+ * Internal withdraw handle used when withdrawing tips.
+ */
+struct WithdrawHandle
+{
+  /**
+   * Withdraw operation this handle represents.
+   */
+  struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh;
+
+  /**
+   * Interpreter state we are part of.
+   */
+  struct TALER_TESTING_Interpreter *is;
+
+  /**
+   * Offset of this withdraw operation in the current
+   * @e is command.
+   */
+  unsigned int off;
+
+};
+
+/**
+ * Callbacks of this type are used to serve the result of
+ * submitting a withdraw request to a exchange.
+ *
+ * @param cls closure, a `struct WithdrawHandle *`
+ * @param http_status HTTP response code, #MHD_HTTP_OK (200)
+ *        for successful status request, 0 if the exchange's
+ *        reply is bogus (fails to follow the protocol)
+ * @param ec taler-specific error code, #TALER_EC_NONE on success
+ * @param sig signature over the coin, NULL on error
+ * @param full_response full response from the exchange
+ *        (for logging, in case of errors)
+ */
+static void
+pickup_withdraw_cb (void *cls,
+                    unsigned int http_status,
+                    enum TALER_ErrorCode ec,
+                    const struct TALER_DenominationSignature *sig,
+                    const json_t *full_response)
+{
+  struct WithdrawHandle *wh = cls;
+  struct TALER_TESTING_Interpreter *is = wh->is;
+  struct TipPickupState *tps = is->commands[is->ip].cls;
+
+  wh->wsh = NULL;
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Withdraw operation %u completed with %u (%d)\n",
+              wh->off,
+              http_status,
+              ec);
+  GNUNET_assert (wh->off < tps->num_coins);
+  if ( (MHD_HTTP_OK != http_status) ||
+       (TALER_EC_NONE != ec) )
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d)"
+                " to command %s when withdrawing\n",
+                http_status,
+                ec,
+                TALER_TESTING_interpreter_get_current_label (is));
+    TALER_TESTING_interpreter_fail (is);
+    return;
+  }
+  if (NULL == tps->sigs)
+    tps->sigs = GNUNET_new_array
+      (tps->num_coins, struct TALER_DenominationSignature);
+
+  GNUNET_assert (NULL == tps->sigs[wh->off].rsa_signature);
+  tps->sigs[wh->off].rsa_signature
+    = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature);
+
+  for (unsigned int i=0; i<tps->num_coins; i++)
+    if (NULL != tps->withdraws[wh->off].wsh)
+      return; /* still some ops ongoing */
+
+  GNUNET_free (tps->withdraws);
+  tps->withdraws = NULL;
+  TALER_TESTING_interpreter_next (is);
+}
+
+
+/**
+ * Callback for a /tip-pickup request.  Returns the result of
+ * the operation.
+ *
+ * @param cls closure
+ * @param http_status HTTP status returned by the merchant
+ *        backend, "200 OK" on success
+ * @param ec taler-specific error code
+ * @param reserve_pub public key of the reserve that made the
+ *        @a reserve_sigs, NULL on error
+ * @param num_reserve_sigs length of the @a reserve_sigs array,
+ *        0 on error
+ * @param reserve_sigs array of signatures authorizing withdrawals,
+ *        NULL on error
+ * @param json original json response
+ */
+static void
+pickup_cb (void *cls,
+           unsigned int http_status,
+           enum TALER_ErrorCode ec,
+           const struct TALER_ReservePublicKeyP *reserve_pub,
+           unsigned int num_reserve_sigs,
+           const struct TALER_ReserveSignatureP *reserve_sigs,
+           const json_t *json)
+{
+  struct TipPickupState *tps = cls;
+
+  tps->tpo = NULL;
+  if (http_status != tps->http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                http_status,
+                ec,
+                TALER_TESTING_interpreter_get_current_label
+                  (tps->is));
+    TALER_TESTING_FAIL (tps->is);
+  }
+
+  if (ec != tps->expected_ec)
+    TALER_TESTING_FAIL (tps->is);
+
+
+  if ( (MHD_HTTP_OK != http_status) ||
+       (TALER_EC_NONE != ec) )
+  {
+    TALER_TESTING_interpreter_next (tps->is);
+    return;
+  }
+  if (num_reserve_sigs != tps->num_coins)
+    TALER_TESTING_FAIL (tps->is);
+
+
+  /* pickup successful, now withdraw! */
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Obtained %u signatures for withdrawal"
+              " from picking up a tip\n",
+              num_reserve_sigs);
+  GNUNET_assert (NULL == tps->withdraws);
+  tps->withdraws = GNUNET_new_array
+    (num_reserve_sigs, struct WithdrawHandle);
+
+  for (unsigned int i=0;i<num_reserve_sigs;i++)
+  {
+    struct WithdrawHandle *wh = &tps->withdraws[i];
+
+    wh->off = i;
+    wh->is = tps->is;
+    GNUNET_assert
+      ( (NULL == wh->wsh) &&
+        ( (NULL == tps->sigs) ||
+          (NULL == tps->sigs[wh->off].rsa_signature) ) );
+    wh->wsh = TALER_EXCHANGE_reserve_withdraw2
+      (tps->exchange,
+       tps->dks[i],
+       &reserve_sigs[i],
+       reserve_pub,
+       &tps->psa[i],
+       &pickup_withdraw_cb,
+       wh);
+  if (NULL == wh->wsh)
+    TALER_TESTING_FAIL (tps->is);
+  }
+  if (0 == num_reserve_sigs)
+    TALER_TESTING_interpreter_next (tps->is);
+}
+
+static void
+tip_pickup_run (void *cls,
+                const struct TALER_TESTING_Command *cmd,
+                struct TALER_TESTING_Interpreter *is)
+{
+  struct TipPickupState *tps = cls;
+  unsigned int num_planchets;
+  const struct TALER_TESTING_Command *replay_cmd;
+  const struct TALER_TESTING_Command *authorize_cmd;
+  struct TALER_Amount amount;
+  const struct GNUNET_HashCode *tip_id;
+
+  tps->is = is;
+  if (NULL == tps->replay_reference)
+  {
+    replay_cmd = NULL;
+
+    /* Count planchets. */
+    for (num_planchets=0;
+         NULL != tps->amounts[num_planchets];
+         num_planchets++);
+  }
+  else
+  {
+    unsigned int *np;
+    if ( NULL == /* looking for "parent" tip-pickup command */
+       ( replay_cmd = TALER_TESTING_interpreter_lookup_command
+         (is, tps->replay_reference)) )
+    TALER_TESTING_FAIL (is);   
+    
+    if (GNUNET_OK != TALER_TESTING_get_trait_uint
+        (replay_cmd, 0, &np))
+      TALER_TESTING_FAIL (is);
+    num_planchets = *np;
+  }
+
+  if (NULL ==
+     ( authorize_cmd = TALER_TESTING_interpreter_lookup_command
+       (is, tps->authorize_reference)) )
+    TALER_TESTING_FAIL (is);
+
+  tps->num_coins = num_planchets;
+  {
+    struct TALER_PlanchetDetail planchets[num_planchets];
+
+    tps->psa = GNUNET_new_array (num_planchets,
+                                 struct TALER_PlanchetSecretsP);
+    tps->dks = GNUNET_new_array
+      (num_planchets,
+       const struct TALER_EXCHANGE_DenomPublicKey *);
+
+    for (unsigned int i=0;i<num_planchets;i++)
+    {
+      if (NULL == replay_cmd)
+      {
+        GNUNET_assert (GNUNET_OK == TALER_string_to_amount
+          (tps->amounts[i], &amount));
+
+        tps->dks[i] = TALER_TESTING_find_pk (is->keys,
+                                             &amount);
+        if (NULL == tps->dks[i])
+          TALER_TESTING_FAIL (is);
+
+        TALER_planchet_setup_random (&tps->psa[i]); 
+      }
+      else
+      {
+        if (GNUNET_OK != TALER_TESTING_get_trait_denom_pub
+            (replay_cmd, i, &tps->dks[i]))
+          TALER_TESTING_FAIL (is);
+
+        struct TALER_PlanchetSecretsP *ps;
+
+        if (GNUNET_OK != TALER_TESTING_get_trait_planchet_secrets
+            (replay_cmd, i, &ps))
+          TALER_TESTING_FAIL (is);
+        tps->psa[i] = *ps;
+      }
+
+      if (GNUNET_OK != TALER_planchet_prepare (&tps->dks[i]->key,
+                                               &tps->psa[i],
+                                               &planchets[i]))
+        TALER_TESTING_FAIL (is); 
+    }
+    
+    if (GNUNET_OK != TALER_TESTING_get_trait_tip_id
+      (authorize_cmd, 0, &tip_id))
+      TALER_TESTING_FAIL (is);
+
+    tps->tpo = TALER_MERCHANT_tip_pickup (tps->ctx,
+                                          tps->merchant_url,
+                                          tip_id,
+                                          num_planchets,
+                                          planchets,
+                                          &pickup_cb,
+                                          tps);
+    GNUNET_assert (NULL != tps->tpo);
+  }
+}
+
+
+static void
+tip_pickup_cleanup (void *cls,
+                    const struct TALER_TESTING_Command *cmd)
+{
+  struct TipPickupState *tps = cls;
+
+  if (NULL != tps->tpo)
+  {
+    TALER_LOG_WARNING ("Tip-pickup operation"
+                       " did not complete\n");
+    TALER_MERCHANT_tip_pickup_cancel (tps->tpo);
+  }
+
+  GNUNET_free (tps);
+}
+
+
+/**
+ * Extract information from a command that is useful for other
+ * commands.
+ *
+ * @param cls closure
+ * @param ret[out] result (could be anything)
+ * @param trait name of the trait
+ * @param selector more detailed information about which object
+ *                 to return in case there were multiple generated
+ *                 by the command
+ * @return #GNUNET_OK on success
+ */
+static int
+tip_pickup_traits (void *cls,
+                   void **ret,
+                   const char *trait,
+                   unsigned int index)
+{
+  struct TipPickupState *tps = cls;
+  struct TALER_TESTING_Trait traits[tps->num_coins + 1];
+  
+  for (unsigned int i=0; i<tps->num_coins; i++)
+    traits[i] = TALER_TESTING_make_trait_planchet_secrets
+      (0, &tps->psa[i]);
+  traits[tps->num_coins + 1] = TALER_TESTING_trait_end ();
+
+  return TALER_TESTING_get_trait (traits,
+                                  ret,
+                                  trait,
+                                  index);
+  return GNUNET_SYSERR;
+}
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_tip_pickup
+  (const char *label,
+   const char *merchant_url,
+   struct GNUNET_CURL_Context *ctx,
+   unsigned int http_status,
+   const char *authorize_reference,
+   const char **amounts,
+   struct TALER_EXCHANGE_Handle *exchange)
+{
+  struct TipPickupState *tps;
+  struct TALER_TESTING_Command cmd;
+
+  tps = GNUNET_new (struct TipPickupState);
+  tps->merchant_url = merchant_url;
+  tps->ctx = ctx;
+  tps->authorize_reference = authorize_reference;
+  tps->amounts = amounts;
+  tps->exchange = exchange;
+
+  cmd.cls = tps;
+  cmd.label = label;
+  cmd.run = &tip_pickup_run;
+  cmd.cleanup = &tip_pickup_cleanup;
+  cmd.traits = &tip_pickup_traits;
+  
+  return cmd;
+}
+
+
+/* end of testing_api_cmd_tip.c */
diff --git a/src/lib/testing_api_cmd_track.c b/src/lib/testing_api_cmd_track.c
new file mode 100644
index 0000000..e665373
--- /dev/null
+++ b/src/lib/testing_api_cmd_track.c
@@ -0,0 +1,362 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 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 lib/testing_api_cmd_track.c
+ * @brief command to test /track/transaction and /track/transfer.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+
+struct TrackTransactionState
+{
+  struct TALER_MERCHANT_TrackTransactionHandle *tth;
+
+  struct TALER_TESTING_Interpreter *is;
+
+  const char *merchant_url;
+
+  struct GNUNET_CURL_Context *ctx;
+
+  unsigned int http_status;
+
+  const char *transfer_reference;
+
+  const char *pay_reference;
+
+  const char *wire_fee;
+};
+
+struct TrackTransferState
+{
+
+  struct TALER_MERCHANT_TrackTransferHandle *tth;
+
+  struct TALER_TESTING_Interpreter *is;
+
+  const char *merchant_url;
+
+  struct GNUNET_CURL_Context *ctx;
+
+  unsigned int http_status;
+
+  const char *check_bank_reference;
+
+  /**
+   * #OC_PAY command which we expect in the result.
+   * Since we are tracking a bank transaction, we want to know
+   * which (Taler) deposit is associated with the bank
+   * transaction being tracked now.
+   */
+  const char *pay_reference;
+};
+
+/**
+ * Function called with detailed wire transfer data.
+ *
+ * @param cls closure
+ * @param http_status HTTP status code we got,
+ *        0 on exchange protocol violation
+ * @param ec taler-specific error code
+ * @param json original json reply
+ */
+static void
+track_transaction_cb (void *cls,
+                      unsigned int http_status,
+                      enum TALER_ErrorCode ec,
+                      const json_t *json)
+{
+  struct TrackTransactionState *tts = cls;
+
+  tts->tth = NULL;
+  if (tts->http_status != http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                http_status,
+                ec,
+                TALER_TESTING_interpreter_get_current_label
+                  (tts->is));
+    TALER_TESTING_interpreter_fail (tts->is);
+    return;
+  }
+  if (MHD_HTTP_OK != http_status)
+    TALER_TESTING_FAIL (tts->is);
+
+  TALER_TESTING_interpreter_next (tts->is);
+}
+
+/**
+ * Callback for a /track/transfer operation
+ *
+ * @param cls closure for this function
+ * @param http_status HTTP response code returned by the server
+ * @param ec taler-specific error code
+ * @param sign_key exchange key used to sign @a json, or NULL
+ * @param json original json reply (may include signatures,
+ *        those have then been validated already)
+ * @param h_wire hash of the wire transfer address the transfer
+ *        went to, or NULL on error
+ * @param total_amount total amount of the wire transfer, or NULL
+ *        if the exchange could not provide any @a wtid (set only
+ *        if @a http_status is #MHD_HTTP_OK)
+ * @param details_length length of the @a details array
+ * @param details array with details about the combined
+ *        transactions
+ */
+static void
+track_transfer_cb
+  (void *cls,
+   unsigned int http_status,
+   enum TALER_ErrorCode ec,
+   const struct TALER_ExchangePublicKeyP *sign_key,
+   const json_t *json,
+   const struct GNUNET_HashCode *h_wire,
+   const struct TALER_Amount *total_amount,
+   unsigned int details_length,
+   const struct TALER_MERCHANT_TrackTransferDetails *details)
+{
+  /* FIXME, deeper checks should be implemented here. */
+  struct TrackTransferState *tts = cls;
+
+  tts->tth = NULL;
+  if (tts->http_status != http_status)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u (%d) to command %s\n",
+                http_status,
+                ec,
+                TALER_TESTING_interpreter_get_current_label
+                  (tts->is));
+    TALER_TESTING_interpreter_fail (tts->is);
+    return;
+  }
+  switch (http_status)
+  {
+    case MHD_HTTP_OK:
+      break;
+    default:
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  "Unhandled HTTP status.\n");
+  }
+  TALER_TESTING_interpreter_next (tts->is);
+}
+
+
+
+/**
+ * Runs the command.  Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks.  Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @param is interpreter state
+ */
+static void
+track_transfer_run (void *cls,
+                    const struct TALER_TESTING_Command *cmd,
+                    struct TALER_TESTING_Interpreter *is)
+{
+  struct TrackTransferState *tts = cls;
+  struct TALER_WireTransferIdentifierRawP *wtid;
+  const struct TALER_TESTING_Command *check_bank_cmd;
+  const char *exchange_url;
+
+  tts->is = is;
+  check_bank_cmd = TALER_TESTING_interpreter_lookup_command
+    (is, tts->check_bank_reference);
+  if (NULL == check_bank_cmd)
+    TALER_TESTING_FAIL (is);
+  if (GNUNET_OK != TALER_TESTING_get_trait_wtid
+      (check_bank_cmd, 0, &wtid))
+    TALER_TESTING_FAIL (is);
+  if (GNUNET_OK != TALER_TESTING_get_trait_url
+      (check_bank_cmd, 0, &exchange_url))
+    TALER_TESTING_FAIL (is);
+  tts->tth = TALER_MERCHANT_track_transfer (tts->ctx,
+                                            tts->merchant_url,
+                                            "default",
+                                            "test",
+                                            wtid,
+                                            exchange_url,
+                                            &track_transfer_cb,
+                                            tts);
+  GNUNET_assert (NULL != tts->tth); 
+}
+
+/**
+ * Runs the command.  Note that upon return, the interpreter
+ * will not automatically run the next command, as the command
+ * may continue asynchronously in other scheduler tasks.  Thus,
+ * the command must ensure to eventually call
+ * #TALER_TESTING_interpreter_next() or
+ * #TALER_TESTING_interpreter_fail().
+ *
+ * @param is interpreter state
+ */
+static void
+track_transaction_run (void *cls,
+                       const struct TALER_TESTING_Command *cmd,
+                       struct TALER_TESTING_Interpreter *is)
+{
+  struct TrackTransactionState *tts = cls;
+  const char *order_id;
+  const struct TALER_TESTING_Command *pay_cmd;
+
+  tts->is = is;
+
+  if ( NULL ==
+     ( pay_cmd = TALER_TESTING_interpreter_lookup_command
+      (is, tts->pay_reference)))
+    TALER_TESTING_FAIL (is);
+
+  if (GNUNET_OK != TALER_TESTING_get_trait_order_id
+      (pay_cmd, 0, &order_id))
+    TALER_TESTING_FAIL (is);
+
+  tts->tth = TALER_MERCHANT_track_transaction
+    (tts->ctx,
+     tts->merchant_url,
+     "default",
+     order_id,
+     &track_transaction_cb,
+     tts);
+  
+  GNUNET_assert (NULL != tts->tth);
+}
+
+
+/**
+ * Clean up after the command.  Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+track_transfer_cleanup (void *cls,
+                        const struct TALER_TESTING_Command *cmd)
+{
+  struct TrackTransferState *tts = cls;
+
+  if (NULL != tts->tth)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "/track/transfer (test) operation"
+                " did not complete\n");
+    TALER_MERCHANT_track_transfer_cancel (tts->tth);
+  }
+  GNUNET_free (tts);
+}
+
+/**
+ * Clean up after the command.  Run during forced termination
+ * (CTRL-C) or test failure or test success.
+ *
+ * @param cls closure
+ */
+static void
+track_transaction_cleanup (void *cls,
+                           const struct TALER_TESTING_Command *cmd)
+{
+  struct TrackTransactionState *tts = cls;
+
+  if (NULL != tts->tth)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "/track/transaction (test) operation"
+                " did not complete\n");
+    TALER_MERCHANT_track_transaction_cancel (tts->tth);
+  }
+  GNUNET_free (tts);
+}
+
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_track_transaction
+  (const char *label,
+   const char *merchant_url,
+   struct GNUNET_CURL_Context *ctx,
+   unsigned int http_status,
+   const char *transfer_reference,
+   const char *pay_reference,
+   const char *wire_fee)
+{
+  struct TrackTransactionState *tts;
+  struct TALER_TESTING_Command cmd;
+
+  tts = GNUNET_new (struct TrackTransactionState);
+  tts->merchant_url = merchant_url;
+  tts->ctx = ctx;
+  tts->http_status = http_status;
+  tts->transfer_reference = transfer_reference;
+  tts->pay_reference = pay_reference;
+  tts->wire_fee = wire_fee;
+
+  cmd.cls = tts;
+  cmd.label = label;
+  cmd.run = &track_transaction_run;
+  cmd.cleanup = &track_transaction_cleanup;
+  // traits?
+
+  return cmd;
+}
+
+
+/**
+ * FIXME
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_track_transfer
+  (const char *label,
+   const char *merchant_url,
+   struct GNUNET_CURL_Context *ctx,
+   unsigned int http_status,
+   const char *check_bank_reference,
+   const char *pay_reference)
+{
+  struct TrackTransferState *tts;
+  struct TALER_TESTING_Command cmd;
+
+  tts = GNUNET_new (struct TrackTransferState);
+  tts->merchant_url = merchant_url;
+  tts->ctx = ctx;
+  tts->http_status = http_status;
+  tts->check_bank_reference = check_bank_reference;
+  tts->pay_reference = pay_reference;
+
+  cmd.cls = tts;
+  cmd.label = label;
+  cmd.run = &track_transfer_run;
+  cmd.cleanup = &track_transfer_cleanup;
+  // traits?
+
+  return cmd;
+}
+
+/* end of testing_api_cmd_track.c */
diff --git a/src/lib/testing_api_helpers.c b/src/lib/testing_api_helpers.c
new file mode 100644
index 0000000..e445856
--- /dev/null
+++ b/src/lib/testing_api_helpers.c
@@ -0,0 +1,185 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 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 lib/testing_api_helpers.c
+ * @brief helper functions for test library.
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_testing_lib.h"
+
+
+/**
+ * Start the merchant backend process.  Assume the port
+ * is available and the database is clean.  Use the "prepare
+ * merchant" function to do such tasks.
+ *
+ * @param config_filename configuration filename.
+ *
+ * @return the process, or NULL if the process could not
+ *         be started.
+ */
+struct GNUNET_OS_Process *
+TALER_TESTING_run_merchant (const char *config_filename)
+{
+  struct GNUNET_OS_Process *merchant_proc;
+  unsigned int iter;
+
+  merchant_proc
+    = GNUNET_OS_start_process (GNUNET_NO,
+                               GNUNET_OS_INHERIT_STD_ALL,
+                               NULL, NULL, NULL,
+                               "taler-merchant-httpd",
+                               "taler-merchant-httpd",
+                               "-c", config_filename,
+                               NULL);
+  if (NULL == merchant_proc)
+    MERCHANT_FAIL ();
+
+  /* give child time to start and bind against the socket */
+  fprintf (stderr,
+           "Waiting for `taler-merchant-httpd' to be ready");
+  iter = 0;
+  do
+    {
+      if (10 == iter)
+      {
+       fprintf (
+          stderr,
+         "Failed to launch `taler-merchant-httpd' (or `wget')\n");
+       GNUNET_OS_process_kill (merchant_proc,
+                               SIGTERM);
+       GNUNET_OS_process_wait (merchant_proc);
+       GNUNET_OS_process_destroy (merchant_proc);
+       MERCHANT_FAIL ();
+      }
+      fprintf (stderr, ".");
+      sleep (1);
+      iter++;
+    }
+  while (0 != system (
+    "wget -q -t 1 -T 1 http://127.0.0.1:8082/"; \
+    " -o /dev/null -O /dev/null"));
+  fprintf (stderr, "\n");
+
+  return merchant_proc;
+}
+
+
+/**
+ * Prepare the merchant execution.  Create tables and check if
+ * the port is available.
+ *
+ * @param config_filename configuration filename.
+ *
+ * @return the base url, or NULL upon errors.  Must be freed
+ *         by the caller.
+ */
+char *
+TALER_TESTING_prepare_merchant (const char *config_filename)
+{
+
+  struct GNUNET_CONFIGURATION_Handle *cfg;
+  unsigned long long port;
+  struct GNUNET_OS_Process *dbinit_proc;
+  enum GNUNET_OS_ProcessStatusType type;
+  unsigned long code;
+  char *base_url;
+
+  cfg = GNUNET_CONFIGURATION_create ();
+
+  if (GNUNET_OK != GNUNET_CONFIGURATION_load
+      (cfg, config_filename))
+    MERCHANT_FAIL ();
+
+  if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number
+    (cfg, "merchant",
+     "PORT", &port))
+  {
+    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+                               "merchant",
+                               "PORT");
+    GNUNET_CONFIGURATION_destroy (cfg);
+    MERCHANT_FAIL ();
+  }
+
+  GNUNET_CONFIGURATION_destroy (cfg);
+
+  if (GNUNET_OK != GNUNET_NETWORK_test_port_free
+    (IPPROTO_TCP, (uint16_t) port))
+  {
+    fprintf (stderr,
+             "Required port %llu not available, skipping.\n",
+            port);
+    MERCHANT_FAIL ();
+  }
+
+  /* DB preparation */
+  if (NULL ==
+     (dbinit_proc = GNUNET_OS_start_process (
+       GNUNET_NO,
+       GNUNET_OS_INHERIT_STD_ALL,
+       NULL, NULL, NULL,
+       "taler-merchant-dbinit",
+       "taler-merchant-dbinit",
+       "-c", "test_merchant_api.conf",
+       "-r", NULL)))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Failed to run taler-merchant-dbinit."
+                " Check your PATH.\n");
+    MERCHANT_FAIL ();
+  }
+
+  if (GNUNET_SYSERR ==
+      GNUNET_OS_process_wait_status (dbinit_proc,
+                                     &type,
+                                     &code))
+  {
+    GNUNET_OS_process_destroy (dbinit_proc);
+    MERCHANT_FAIL ();
+  }
+  if ( (type == GNUNET_OS_PROCESS_EXITED) &&
+       (0 != code) )
+  {
+    fprintf (stderr,
+             "Failed to setup database\n");
+    MERCHANT_FAIL ();
+  }
+  if ( (type != GNUNET_OS_PROCESS_EXITED) ||
+       (0 != code) )
+  {
+    fprintf (stderr,
+             "Unexpected error running"
+             " `taler-merchant-dbinit'!\n");
+    MERCHANT_FAIL ();
+  }
+
+  GNUNET_OS_process_destroy (dbinit_proc);
+
+  GNUNET_asprintf (&base_url,
+                   "http://localhost:%llu/";,
+                   port);
+  return base_url;
+}
diff --git a/src/lib/testing_api_trait_hash.c b/src/lib/testing_api_trait_hash.c
new file mode 100644
index 0000000..d2d99d8
--- /dev/null
+++ b/src/lib/testing_api_trait_hash.c
@@ -0,0 +1,117 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018 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 lib/testing_api_trait_hash.c
+ * @brief offer any trait that is passed over as a hash code.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+
+#define TALER_TESTING_TRAIT_TIP_ID "tip-id"
+#define TALER_TESTING_TRAIT_H_CONTRACT_TERMS "h-contract-terms"
+
+/**
+ * Obtain tip id from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which signature to pick if @a cmd has multiple
+ *        on offer
+ * @param tip_id[out] set to the wanted data.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_tip_id
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   struct GNUNET_HashCode **tip_id)
+{
+  return cmd->traits (cmd->cls,
+                      (void **) tip_id,
+                      TALER_TESTING_TRAIT_TIP_ID,
+                      index);
+}
+
+
+/**
+ * Offer tip id.
+ *
+ * @param index which tip id to offer if there are
+ *        multiple on offer
+ * @param planchet_secrets set to the offered secrets.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_tip_id
+  (unsigned int index,
+   const struct GNUNET_HashCode *tip_id)
+{
+  struct TALER_TESTING_Trait ret = {
+    .index = index,
+    .trait_name = TALER_TESTING_TRAIT_TIP_ID,
+    .ptr = (const void *) tip_id
+  };
+  return ret;
+}
+
+
+/**
+ * Obtain contract terms hash from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which hash code to pick if @a cmd has multiple
+ *        on offer
+ * @param h_contract_terms[out] set to the wanted data.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_h_contract_terms
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   const struct GNUNET_HashCode **h_contract_terms)
+{
+  return cmd->traits (cmd->cls,
+                      (void **) h_contract_terms,
+                      TALER_TESTING_TRAIT_H_CONTRACT_TERMS,
+                      index);
+}
+
+
+/**
+ * Offer contract terms hash code.
+ *
+ * @param index which hash code to offer if there are
+ *        multiple on offer
+ * @param h_contract_terms set to the offered hash code.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_h_contract_terms
+  (unsigned int index,
+   const struct GNUNET_HashCode *h_contract_terms)
+{
+  struct TALER_TESTING_Trait ret = {
+    .index = index,
+    .trait_name = TALER_TESTING_TRAIT_H_CONTRACT_TERMS,
+    .ptr = (const void *) h_contract_terms
+  };
+  return ret;
+}
diff --git a/src/lib/testing_api_trait_merchant_sig.c 
b/src/lib/testing_api_trait_merchant_sig.c
new file mode 100644
index 0000000..26bedde
--- /dev/null
+++ b/src/lib/testing_api_trait_merchant_sig.c
@@ -0,0 +1,73 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018 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 lib/testing_api_trait_merchant_sig.c
+ * @brief offer merchant signature over contract
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+
+#define TALER_TESTING_TRAIT_MERCHANT_SIG "reserve-private-key"
+
+/**
+ * Obtain a merchant signature over a contract from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which signature to pick if @a cmd has multiple
+ *        on offer
+ * @param merchant_sig[out] set to the wanted signature.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_merchant_sig
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   struct TALER_MerchantSignatureP **merchant_sig)
+{
+  return cmd->traits (cmd->cls,
+                      (void **) merchant_sig,
+                      TALER_TESTING_TRAIT_MERCHANT_SIG,
+                      index);
+}
+
+/**
+ * Offer a merchant signature over a contract.
+ *
+ * @param index which signature to offer if there are multiple
+ *        on offer
+ * @param merchant_sig set to the offered signature.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_merchant_sig
+  (unsigned int index,
+   const struct TALER_MerchantSignatureP *merchant_sig)
+{
+  struct TALER_TESTING_Trait ret = {
+    .index = index,
+    .trait_name = TALER_TESTING_TRAIT_MERCHANT_SIG,
+    .ptr = (const void *) merchant_sig
+  };
+  return ret;
+}
+
+/* end of testing_api_trait_merchant_sig.c */
diff --git a/src/lib/testing_api_trait_planchet.c 
b/src/lib/testing_api_trait_planchet.c
new file mode 100644
index 0000000..8b225c3
--- /dev/null
+++ b/src/lib/testing_api_trait_planchet.c
@@ -0,0 +1,73 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018 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 lib/testing_api_trait_planchet.c
+ * @brief offer planchet secrets as trait.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+
+#define TALER_TESTING_TRAIT_PLANCHET_SECRETS "planchet-secrets"
+
+/**
+ * Obtain planchet secrets from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which signature to pick if @a cmd has multiple
+ *        on offer
+ * @param planchet_secrets[out] set to the wanted secrets.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_planchet_secrets
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   struct TALER_PlanchetSecretsP **planchet_secrets)
+{
+  return cmd->traits (cmd->cls,
+                      (void **) planchet_secrets,
+                      TALER_TESTING_TRAIT_PLANCHET_SECRETS,
+                      index);
+}
+
+/**
+ * Offer planchet secrets.
+ *
+ * @param index which secrets to offer if there are multiple
+ *        on offer
+ * @param planchet_secrets set to the offered secrets.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_planchet_secrets
+  (unsigned int index,
+   const struct TALER_PlanchetSecretsP *planchet_secrets)
+{
+  struct TALER_TESTING_Trait ret = {
+    .index = index,
+    .trait_name = TALER_TESTING_TRAIT_PLANCHET_SECRETS,
+    .ptr = (const void *) planchet_secrets
+  };
+  return ret;
+}
+
+/* end of testing_api_trait_planchet.c */
diff --git a/src/lib/testing_api_trait_refund_entry.c 
b/src/lib/testing_api_trait_refund_entry.c
new file mode 100644
index 0000000..c5728b9
--- /dev/null
+++ b/src/lib/testing_api_trait_refund_entry.c
@@ -0,0 +1,77 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2014-2018 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 lib/testing_api_trait_refund_entry.c
+ * @brief command to offer refund entry trait.
+ * @author Marcello Stanisci
+ */
+
+#include "platform.h"
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+
+#define TALER_TESTING_TRAIT_REFUND_ENTRY "refund-entry"
+
+/**
+ * Obtain refund entry from a @a cmd.
+ *
+ * @param cmd command to extract trait from
+ * @param index which signature to pick if @a cmd has multiple
+ *        on offer
+ * @param refund_entry[out] set to the wanted data.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_refund_entry
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   const struct TALER_MERCHANT_RefundEntry **refund_entry)
+{
+  return cmd->traits (cmd->cls,
+                      (void **) refund_entry,
+                      TALER_TESTING_TRAIT_REFUND_ENTRY,
+                      index);
+}
+
+/**
+ * Offer refund entry.
+ *
+ * @param index which tip id to offer if there are
+ *        multiple on offer
+ * @param refund_entry set to the offered refund entry.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_refund_entry
+  (unsigned int index,
+   const struct TALER_MERCHANT_RefundEntry *refund_entry)
+{
+  struct TALER_TESTING_Trait ret = {
+    .index = index,
+    .trait_name = TALER_TESTING_TRAIT_REFUND_ENTRY,
+    .ptr = (const void *) refund_entry
+  };
+  return ret;
+}
+
+
+/* end of testing_api_trait_refund_entry.c */
diff --git a/src/lib/testing_api_trait_string.c 
b/src/lib/testing_api_trait_string.c
new file mode 100644
index 0000000..74b1ca0
--- /dev/null
+++ b/src/lib/testing_api_trait_string.c
@@ -0,0 +1,131 @@
+/*
+  This file is part of TALER
+  Copyright (C) 2018 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 lib/testing_api_trait_string.c
+ * @brief offer traits that come as strings.
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+
+#define TALER_TESTING_TRAIT_PROPOSAL_REFERENCE "proposal-reference"
+#define TALER_TESTING_TRAIT_COIN_REFERENCE "coin-reference"
+
+/**
+ * Obtain a reference to a proposal command.  Any command that
+ * works with proposals, might need to offer their reference to
+ * it.  Notably, the "pay" command, offers its proposal reference
+ * to the "pay abort" command as the latter needs to reconstruct
+ * the same data needed by the former in order to use the "pay
+ * abort" API.
+ *
+ * @param cmd command to extract trait from
+ * @param index which reference to pick if @a cmd has multiple
+ *        on offer
+ * @param proposal_reference[out] set to the wanted reference.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_proposal_reference
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   const char **proposal_reference)
+{
+  return cmd->traits (cmd->cls,
+                      (void **) proposal_reference,
+                      TALER_TESTING_TRAIT_PROPOSAL_REFERENCE,
+                      index);
+}
+
+
+/**
+ * Offer a proposal reference.
+ *
+ * @param index which reference to offer if there are
+ *        multiple on offer
+ * @param proposal_reference set to the offered reference.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_proposal_reference
+  (unsigned int index,
+   const char *proposal_reference)
+{
+  struct TALER_TESTING_Trait ret = {
+    .index = index,
+    .trait_name = TALER_TESTING_TRAIT_PROPOSAL_REFERENCE,
+    .ptr = (const void *) proposal_reference
+  };
+  return ret;
+}
+
+
+/**
+ * Obtain a reference to any command that can provide coins as
+ * traits.
+ *
+ * @param cmd command to extract trait from
+ * @param index which reference to pick if @a cmd has multiple
+ *        on offer
+ * @param coin_reference[out] set to the wanted reference. NOTE:
+ *        a _single_ reference can contain _multiple_ instances,
+ *        using semi-colon as separator.  For example, a _single_
+ *        reference can be this: "coin-ref-1", or even this:
+ *        "coin-ref-1;coin-ref-2".  The "pay" command contains
+ *        functions that can parse such format.
+ * @return #GNUNET_OK on success
+ */
+int
+TALER_TESTING_get_trait_coin_reference
+  (const struct TALER_TESTING_Command *cmd,
+   unsigned int index,
+   const char **coin_reference)
+{
+  return cmd->traits (cmd->cls,
+                      (void **) coin_reference,
+                      TALER_TESTING_TRAIT_COIN_REFERENCE,
+                      index);
+}
+
+
+/**
+ * Offer a coin reference.
+ *
+ * @param index which reference to offer if there are
+ *        multiple on offer
+ * @param coin_reference set to the offered reference.
+ * @return the trait
+ */
+struct TALER_TESTING_Trait
+TALER_TESTING_make_trait_coin_reference
+  (unsigned int index,
+   const char *coin_reference)
+{
+  struct TALER_TESTING_Trait ret = {
+    .index = index,
+    .trait_name = TALER_TESTING_TRAIT_COIN_REFERENCE,
+    .ptr = (const void *) coin_reference
+  };
+  return ret;
+}
+
+
+/* end of testing_api_trait_string.c */

-- 
To stop receiving notification emails like this one, please contact
address@hidden



reply via email to

[Prev in Thread] Current Thread [Next in Thread]