/*
This file is part of TALER
Copyright (C) 2014-2018, 2020 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
*/
/**
* @file testing_api_cmd_pay_order.c
* @brief command to test the /orders/ID/pay feature.
* @author Marcello Stanisci
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "taler_merchant_service.h"
#include "taler_merchant_testing_lib.h"
/**
* State for a /pay CMD.
*/
struct PayState
{
/**
* Contract terms hash code.
*/
struct TALER_PrivateContractHashP h_contract_terms;
/**
* The interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
/**
* Expected HTTP response status code.
*/
unsigned int http_status;
/**
* Reference to a command that can provide a order id,
* typically a /proposal test command.
*/
const char *proposal_reference;
/**
* Reference to a command that can provide a coin, so
* we can pay here.
*/
const char *coin_reference;
/**
* Reference to a command that can provide one or
* multiple tokens used as inputs for the payment.
* In the form "LABEL0[/INDEX];LABEL1[/INDEX];..."
*/
const char *token_reference;
/**
* The merchant base URL.
*/
const char *merchant_url;
/**
* Total amount to be paid.
*/
struct TALER_Amount total_amount;
/**
* Amount to be paid, plus the deposit fee.
*/
const char *amount_with_fee;
/**
* Amount to be paid, including NO fees.
*/
const char *amount_without_fee;
/**
* Handle to the pay operation.
*/
struct TALER_MERCHANT_OrderPayHandle *oph;
/**
* Signature from the merchant, set on success.
*/
struct TALER_MerchantSignatureP merchant_sig;
/**
* Array of issued tokens, set on success.
*/
struct TALER_MERCHANT_PrivateTokenDetails *issued_tokens;
/**
* Number of tokens in @e issued_tokens.
*/
unsigned int num_issued_tokens;
/**
* The session for which the payment is made.
*/
const char *session_id;
/**
* base64-encoded key
*/
const char *pos_key;
/**
* Option that add amount of the order
*/
enum TALER_MerchantConfirmationAlgorithm pos_alg;
/**
* Index of the choice to be used in the payment. -1 for orders without choices.
*/
int choice_index;
};
/**
* Find the token issue public key for a given token family @a slug and
* @a valid_after timestamp.
*
* @param token_families json object of token families where the key is the slug
* @param slug the slug of the token family
* @param valid_after the timestamp of the token family
* @param[out] pub the token issue public key of the token family
* @return #GNUNET_OK on success and #GNUNET_SYSERR if not found
*/
static enum GNUNET_GenericReturnValue
find_token_public_key (const json_t *token_families,
const char *slug,
struct GNUNET_TIME_Timestamp valid_after,
struct TALER_TokenIssuePublicKeyP *pub)
{
const json_t *tf = json_object_get (token_families, slug);
const json_t *keys;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_array_const ("keys",
&keys),
GNUNET_JSON_spec_end ()
};
if (NULL == tf)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Token family `%s' not found\n",
slug);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
GNUNET_JSON_parse (tf,
spec,
NULL,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to parse token family `%s'\n",
slug);
return GNUNET_SYSERR;
}
{
unsigned int i;
const json_t *key;
json_array_foreach (keys, i, key)
{
int64_t cipher;
struct GNUNET_TIME_Timestamp ivalid_after;
struct GNUNET_CRYPTO_BlindSignPublicKey *issue_pub = GNUNET_new (struct
GNUNET_CRYPTO_BlindSignPublicKey);
const char *error_name;
unsigned int error_line;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_fixed_auto ("h_pub",
&issue_pub->pub_key_hash),
GNUNET_JSON_spec_rsa_public_key ("rsa_pub",
&issue_pub->details.rsa_public_key),
// GNUNET_JSON_spec_fixed_auto ("cs_pub",
// &key.pub.public_key->details.cs_public_key)),
GNUNET_JSON_spec_int64 ("cipher",
&cipher),
GNUNET_JSON_spec_timestamp ("valid_after",
&ivalid_after),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (key,
ispec,
&error_name,
&error_line))
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Failed to parse %s at %u: %s\n",
ispec[error_line].field,
error_line,
error_name);
return GNUNET_SYSERR;
}
switch (cipher)
{
case GNUNET_CRYPTO_BSA_RSA:
issue_pub->cipher = GNUNET_CRYPTO_BSA_RSA;
break;
case GNUNET_CRYPTO_BSA_CS:
issue_pub->cipher = GNUNET_CRYPTO_BSA_CS;
break;
case GNUNET_CRYPTO_BSA_INVALID:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Field 'cipher' invalid in key #%u\n",
i);
return GNUNET_SYSERR;
}
/* Compare valid_after to make sure it matches. */
if (GNUNET_TIME_timestamp_cmp (valid_after, !=, ivalid_after))
{
continue;
}
pub->public_key = issue_pub;
return GNUNET_OK;
}
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Key with valid_after '%s' for token family '%s' not found\n",
GNUNET_TIME_timestamp2s (valid_after),
slug);
return GNUNET_SYSERR;
}
/**
* Parse the @a coins specification and grow the @a pc
* array with the coins found, updating @a npc.
*
* @param[in,out] pc pointer to array of coins found
* @param[in,out] npc length of array at @a pc
* @param[in] coins string specifying coins to add to @a pc,
* clobbered in the process
* @param is interpreter state
* @param amount_with_fee total amount to be paid for a contract.
* @param amount_without_fee to be removed, there is no
* per-contract fee, only per-coin exists.
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
build_coins (struct TALER_MERCHANT_PayCoin **pc,
unsigned int *npc,
char *coins,
struct TALER_TESTING_Interpreter *is,
const char *amount_with_fee,
const char *amount_without_fee)
{
char *token;
struct TALER_EXCHANGE_Keys *keys;
keys = TALER_TESTING_get_keys (is);
if (NULL == keys)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
for (token = strtok (coins, ";");
NULL != token;
token = strtok (NULL, ";"))
{
const struct TALER_TESTING_Command *coin_cmd;
char *ctok;
unsigned int ci;
struct TALER_MERCHANT_PayCoin *icoin;
const struct TALER_EXCHANGE_DenomPublicKey *dpk;
const char *exchange_url;
/* Token syntax is "LABEL[/NUMBER]" */
ctok = strchr (token, '/');
// TODO: Check why ci variable is parsed but not used?
ci = 0;
if (NULL != ctok)
{
*ctok = '\0';
ctok++;
if (1 != sscanf (ctok,
"%u",
&ci))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
}
coin_cmd = TALER_TESTING_interpreter_lookup_command
(is, token);
if (NULL == coin_cmd)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
GNUNET_array_grow (*pc,
*npc,
(*npc) + 1);
icoin = &((*pc)[(*npc) - 1]);
{
const struct TALER_CoinSpendPrivateKeyP *coin_priv;
const struct TALER_DenominationSignature *denom_sig;
const struct TALER_Amount *denom_value;
const struct TALER_EXCHANGE_DenomPublicKey *denom_pub;
const struct TALER_AgeCommitmentHash *h_age_commitment;
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_coin_priv (coin_cmd,
0,
&coin_priv));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_denom_pub (coin_cmd,
0,
&denom_pub));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_denom_sig (coin_cmd,
0,
&denom_sig));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_amount (coin_cmd,
&denom_value));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_h_age_commitment (coin_cmd,
0,
&h_age_commitment
));
icoin->coin_priv = *coin_priv;
icoin->denom_pub = denom_pub->key;
icoin->denom_sig = *denom_sig;
icoin->denom_value = *denom_value;
icoin->amount_with_fee = *denom_value;
icoin->h_age_commitment = h_age_commitment;
}
GNUNET_assert (NULL != (dpk =
TALER_TESTING_find_pk (keys,
&icoin->denom_value,
false)));
GNUNET_assert (0 <=
TALER_amount_subtract (&icoin->amount_without_fee,
&icoin->denom_value,
&dpk->fees.deposit));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_exchange_url (coin_cmd,
&exchange_url));
icoin->exchange_url = exchange_url;
}
return GNUNET_OK;
}
/**
* Parse the @a pay_references specification and grow the @a tokens
* array with the tokens found, updating @a tokens_num.
*
* @param[in,out] tokens array of tokens found
* @param[in,out] tokens_num length of @a tokens array
* @param[in] pay_references string of ; separated references to pay commands
that issued the tokens.
* @param is interpreter state
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
build_tokens (struct TALER_MERCHANT_UseToken **tokens,
unsigned int *tokens_num,
char *pay_references,
struct TALER_TESTING_Interpreter *is)
{
char *ref;
for (ref = strtok (pay_references, ";");
NULL != ref;
ref = strtok (NULL, ";"))
{
const struct TALER_TESTING_Command *pay_cmd;
char *slash;
unsigned int index;
struct TALER_MERCHANT_UseToken *token;
/* Reference syntax is "LABEL[/NUMBER]" */
slash = strchr (ref, '/');
index = 0;
if (NULL != slash)
{
*slash = '\0';
slash++;
if (1 != sscanf (slash,
"%u",
&index))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
}
pay_cmd = TALER_TESTING_interpreter_lookup_command (is, ref);
if (NULL == pay_cmd)
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
GNUNET_array_grow (*tokens,
*tokens_num,
(*tokens_num) + 1);
token = &((*tokens)[(*tokens_num) - 1]);
{
const struct TALER_TokenUsePrivateKeyP *token_priv;
const struct TALER_TokenIssueSignatureP *issue_sig;
const struct TALER_TokenIssuePublicKeyP *issue_pub;
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_token_priv (pay_cmd,
index,
&token_priv));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_token_issue_sig (pay_cmd,
index,
&issue_sig));
GNUNET_assert (GNUNET_OK ==
TALER_TESTING_get_trait_token_issue_pub (pay_cmd,
index,
&issue_pub));
token->token_priv = *token_priv;
token->ub_sig = *issue_sig;
token->issue_pub = *issue_pub;
}
}
return GNUNET_OK;
}
/**
* Function called with the result of a /pay operation.
* Checks whether the merchant signature is valid and the
* HTTP response code matches our expectation.
*
* @param cls closure with the interpreter state
* @param pr HTTP response
*/
static void
pay_cb (void *cls,
const struct TALER_MERCHANT_PayResponse *pr)
{
struct PayState *ps = cls;
ps->oph = NULL;
if (ps->http_status != pr->hr.http_status)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u (%d) to command (%s) %s\n",
pr->hr.http_status,
(int) pr->hr.ec,
pr->hr.hint,
TALER_TESTING_interpreter_get_current_label (ps->is));
TALER_TESTING_FAIL (ps->is);
}
if (MHD_HTTP_OK == pr->hr.http_status)
{
ps->merchant_sig = pr->details.ok.merchant_sig;
if (ps->num_issued_tokens != pr->details.ok.num_tokens)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected number of tokens issued. "
"Sent %d envelopes but got %d tokens issued.\n",
ps->num_issued_tokens,
pr->details.ok.num_tokens);
GNUNET_break (0);
TALER_TESTING_interpreter_fail (ps->is);
return;
}
for (unsigned int i = 0; i < ps->num_issued_tokens; i++)
{
struct TALER_MERCHANT_PrivateTokenDetails *details =
&ps->issued_tokens[i];
/* The issued tokens should be in the
same order as the provided envelopes. */
ps->issued_tokens[i].blinded_sig = pr->details.ok.tokens[i].blinded_sig;
if (GNUNET_OK !=
TALER_token_issue_sig_unblind (&details->issue_sig,
&details->blinded_sig,
&details->blinding_secret,
&details->h_token_pub,
&details->blinding_inputs,
&details->issue_pub))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to unblind token signature\n");
GNUNET_break (0);
TALER_TESTING_interpreter_fail (ps->is);
return;
}
}
if (NULL != ps->pos_key)
{
char *pc;
bool found = false;
if (NULL == pr->details.ok.pos_confirmation)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (ps->is);
return;
}
pc = TALER_build_pos_confirmation (ps->pos_key,
ps->pos_alg,
&ps->total_amount,
GNUNET_TIME_timestamp_get ());
/* Check if *any* of our TOTP codes overlaps
with any of the returned TOTP codes. */
for (const char *tok = strtok (pc, "\n");
NULL != tok;
tok = strtok (NULL, "\n"))
{
if (NULL != strstr (pr->details.ok.pos_confirmation,
tok))
{
found = true;
break;
}
}
GNUNET_free (pc);
if (! found)
{
GNUNET_break (0);
TALER_TESTING_interpreter_fail (ps->is);
return;
}
}
}
TALER_TESTING_interpreter_next (ps->is);
}
/**
* Run a "pay" CMD.
*
* @param cls closure.
* @param cmd current CMD being run.
* @param is interpreter state.
*/
static void
pay_run (void *cls,
const struct TALER_TESTING_Command *cmd,
struct TALER_TESTING_Interpreter *is)
{
struct PayState *ps = cls;
const struct TALER_TESTING_Command *proposal_cmd;
const json_t *contract_terms;
const char *order_id;
struct GNUNET_TIME_Timestamp refund_deadline;
struct GNUNET_TIME_Timestamp pay_deadline;
struct GNUNET_TIME_Timestamp timestamp;
struct TALER_MerchantPublicKeyP merchant_pub;
struct TALER_MerchantWireHashP h_wire;
const struct TALER_PrivateContractHashP *h_proposal;
struct TALER_Amount max_fee;
const json_t *choices = NULL;
const json_t *token_families = NULL;
const char *error_name = NULL;
unsigned int error_line = 0;
struct TALER_MERCHANT_PayCoin *pay_coins;
unsigned int npay_coins;
struct TALER_MERCHANT_UseToken *use_tokens = NULL;
unsigned int len_use_tokens = 0;
struct TALER_MERCHANT_OutputToken *output_tokens = NULL;
unsigned int len_output_tokens = 0;
const struct TALER_MerchantSignatureP *merchant_sig;
const enum TALER_MerchantConfirmationAlgorithm *alg_ptr;
ps->is = is;
proposal_cmd = TALER_TESTING_interpreter_lookup_command (
is,
ps->proposal_reference);
if (NULL == proposal_cmd)
TALER_TESTING_FAIL (is);
if (GNUNET_OK !=
TALER_TESTING_get_trait_contract_terms (proposal_cmd,
&contract_terms))
TALER_TESTING_FAIL (is);
if (NULL == contract_terms)
TALER_TESTING_FAIL (is);
if (GNUNET_OK !=
TALER_TESTING_get_trait_otp_key (proposal_cmd,
&ps->pos_key))
ps->pos_key = NULL;
if ( (GNUNET_OK ==
TALER_TESTING_get_trait_otp_alg (proposal_cmd,
&alg_ptr)) &&
(NULL != alg_ptr) )
ps->pos_alg = *alg_ptr;
{
/* Get information that needs to be put verbatim in the
* deposit permission */
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("order_id",
&order_id),
GNUNET_JSON_spec_timestamp ("refund_deadline",
&refund_deadline),
GNUNET_JSON_spec_timestamp ("pay_deadline",
&pay_deadline),
GNUNET_JSON_spec_timestamp ("timestamp",
×tamp),
GNUNET_JSON_spec_fixed_auto ("merchant_pub",
&merchant_pub),
GNUNET_JSON_spec_fixed_auto ("h_wire",
&h_wire),
TALER_JSON_spec_amount_any ("amount",
&ps->total_amount),
TALER_JSON_spec_amount_any ("max_fee",
&max_fee),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_object_const ("token_families",
&token_families),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const ("choices",
&choices),
NULL),
/* FIXME oec: parse minimum age, use data later? */
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (contract_terms,
spec,
&error_name,
&error_line))
{
char *js;
js = json_dumps (contract_terms,
JSON_INDENT (1));
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Parser failed on %s:%u for input `%s'\n",
error_name,
error_line,
js);
free (js);
TALER_TESTING_FAIL (is);
}
}
{
char *cr;
cr = GNUNET_strdup (ps->coin_reference);
pay_coins = NULL;
npay_coins = 0;
if (GNUNET_OK !=
build_coins (&pay_coins,
&npay_coins,
cr,
is,
ps->amount_with_fee,
ps->amount_without_fee))
{
GNUNET_array_grow (pay_coins,
npay_coins,
0);
GNUNET_free (cr);
TALER_TESTING_FAIL (is);
}
GNUNET_free (cr);
}
if (NULL != ps->token_reference)
{
char *tr;
tr = GNUNET_strdup (ps->token_reference);
if (GNUNET_OK !=
build_tokens (&use_tokens,
&len_use_tokens,
tr,
is))
{
GNUNET_array_grow (use_tokens,
len_use_tokens,
0);
GNUNET_free (tr);
TALER_TESTING_FAIL (is);
}
GNUNET_free (tr);
}
if (0 <= ps->choice_index)
{
const json_t *outputs;
json_t *output;
unsigned int output_index;
const json_t *choice = json_array_get (choices, ps->choice_index);
if (NULL == choice)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No choice found at index %d\n",
ps->choice_index);
TALER_TESTING_FAIL (is);
}
{
const char *ierror_name = NULL;
unsigned int ierror_line = 0;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_array_const ("outputs",
&outputs),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (choice,
ispec,
&ierror_name,
&ierror_line))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Parser failed on %s:%u for input `%s'\n",
ierror_name,
ierror_line,
json_dumps (choice,
JSON_INDENT (2)));
TALER_TESTING_FAIL (is);
}
}
json_array_foreach (outputs, output_index, output)
{
const char *slug;
const char *kind;
struct GNUNET_TIME_Timestamp valid_after;
uint32_t count = 1;
const char *ierror_name = NULL;
unsigned int ierror_line = 0;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_string ("kind",
&kind),
GNUNET_JSON_spec_string ("token_family_slug",
&slug),
GNUNET_JSON_spec_timestamp ("valid_after",
&valid_after),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_uint32 ("count",
&count),
NULL),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (output,
ispec,
&ierror_name,
&ierror_line))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Parser failed on %s:%u for input `%s'\n",
ierror_name,
ierror_line,
json_dumps (output,
JSON_INDENT (2)));
TALER_TESTING_FAIL (is);
}
if (0 != strcmp ("token", kind))
{
continue;
}
GNUNET_array_grow (ps->issued_tokens,
ps->num_issued_tokens,
ps->num_issued_tokens + count);
for (unsigned int k = 0; k < count; k++)
{
struct TALER_MERCHANT_PrivateTokenDetails *details =
&ps->issued_tokens[ps->num_issued_tokens - count + k];
if (GNUNET_OK != find_token_public_key (token_families,
slug,
valid_after,
&details->issue_pub))
{
TALER_TESTING_FAIL (is);
}
/* Only RSA is supported for now. */
GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == details->issue_pub.public_key->
cipher);
TALER_token_blind_input_copy (&details->blinding_inputs,
TALER_token_blind_input_rsa_singleton ());
/* TODO: Where to get details->blinding_inputs from? */
TALER_token_use_setup_random (&details->master);
TALER_token_use_setup_priv (&details->master,
&details->blinding_inputs,
&details->token_priv);
TALER_token_use_blinding_secret_create (&details->master,
&details->blinding_inputs,
&details->blinding_secret);
GNUNET_CRYPTO_eddsa_key_get_public (&details->token_priv.private_key,
&details->token_pub.public_key);
GNUNET_CRYPTO_hash (&details->token_pub.public_key,
sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
&details->h_token_pub.hash);
details->envelope.blinded_pub = GNUNET_CRYPTO_message_blind_to_sign (
details->issue_pub.public_key,
&details->blinding_secret,
NULL, /* TODO: Add session nonce to support CS tokens */
&details->h_token_pub.hash,
sizeof (details->h_token_pub.hash),
details->blinding_inputs.blinding_inputs);
if (NULL == details->envelope.blinded_pub)
{
GNUNET_break (0);
TALER_TESTING_FAIL (is);
}
}
}
}
GNUNET_array_grow (output_tokens,
len_output_tokens,
ps->num_issued_tokens);
for (unsigned int i = 0; iissued_tokens[i].envelope.
blinded_pub;
}
if (GNUNET_OK !=
TALER_TESTING_get_trait_merchant_sig (proposal_cmd,
&merchant_sig))
TALER_TESTING_FAIL (is);
if (GNUNET_OK !=
TALER_TESTING_get_trait_h_contract_terms (proposal_cmd,
&h_proposal))
TALER_TESTING_FAIL (is);
ps->h_contract_terms = *h_proposal;
ps->oph = TALER_MERCHANT_order_pay (TALER_TESTING_interpreter_get_context (
is),
ps->merchant_url,
ps->session_id,
h_proposal,
ps->choice_index,
&ps->total_amount,
&max_fee,
&merchant_pub,
merchant_sig,
timestamp,
refund_deadline,
pay_deadline,
&h_wire,
order_id,
npay_coins,
pay_coins,
len_use_tokens,
use_tokens,
len_output_tokens,
output_tokens,
&pay_cb,
ps);
GNUNET_array_grow (pay_coins,
npay_coins,
0);
if (NULL == ps->oph)
TALER_TESTING_FAIL (is);
}
/**
* Free a "pay" CMD, and cancel it if need be.
*
* @param cls closure.
* @param cmd command currently being freed.
*/
static void
pay_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
struct PayState *ps = cls;
if (NULL != ps->oph)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Command `%s' did not complete.\n",
TALER_TESTING_interpreter_get_current_label (
ps->is));
TALER_MERCHANT_order_pay_cancel (ps->oph);
}
GNUNET_free (ps);
}
/**
* Offer internal data useful to other commands.
*
* @param cls closure
* @param[out] ret result
* @param trait name of the trait
* @param index index number of the object to extract.
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
pay_traits (void *cls,
const void **ret,
const char *trait,
unsigned int index)
{
struct PayState *ps = cls;
const char *order_id;
const struct TALER_TESTING_Command *proposal_cmd;
const struct TALER_MerchantPublicKeyP *merchant_pub;
if (NULL != ps->token_reference &&
index >= ps->num_issued_tokens)
{
GNUNET_break (0);
return GNUNET_NO;
}
if (NULL ==
(proposal_cmd =
TALER_TESTING_interpreter_lookup_command (ps->is,
ps->proposal_reference)))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
TALER_TESTING_get_trait_order_id (proposal_cmd,
&order_id))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
if (GNUNET_OK !=
TALER_TESTING_get_trait_merchant_pub (proposal_cmd,
&merchant_pub))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
{
struct TALER_Amount amount_with_fee;
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (ps->amount_with_fee,
&amount_with_fee));
{
struct TALER_TESTING_Trait traits[] = {
TALER_TESTING_make_trait_proposal_reference (ps->proposal_reference),
TALER_TESTING_make_trait_coin_reference (0,
ps->coin_reference),
TALER_TESTING_make_trait_order_id (order_id),
TALER_TESTING_make_trait_merchant_pub (merchant_pub),
TALER_TESTING_make_trait_merchant_sig (&ps->merchant_sig),
TALER_TESTING_make_trait_amount (&amount_with_fee),
TALER_TESTING_make_trait_otp_key (ps->pos_key),
TALER_TESTING_make_trait_otp_alg (&ps->pos_alg),
TALER_TESTING_make_trait_token_priv (index,
&ps->issued_tokens[index].
token_priv),
TALER_TESTING_make_trait_token_issue_pub (index,
&ps->issued_tokens[index].
issue_pub),
TALER_TESTING_make_trait_token_issue_sig (index,
&ps->issued_tokens[index].
issue_sig),
TALER_TESTING_trait_end ()
};
return TALER_TESTING_get_trait (traits,
ret,
trait,
index);
}
}
}
struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_pay_order_choices (const char *label,
const char *merchant_url,
unsigned int http_status,
const char *proposal_reference,
const char *coin_reference,
const char *amount_with_fee,
const char *amount_without_fee,
const char *session_id,
int choice_index,
const char *token_reference)
{
struct PayState *ps;
ps = GNUNET_new (struct PayState);
ps->http_status = http_status;
ps->proposal_reference = proposal_reference;
ps->coin_reference = coin_reference;
ps->merchant_url = merchant_url;
ps->amount_with_fee = amount_with_fee;
ps->amount_without_fee = amount_without_fee;
ps->session_id = session_id;
ps->token_reference = token_reference;
ps->choice_index = choice_index;
{
struct TALER_TESTING_Command cmd = {
.cls = ps,
.label = label,
.run = &pay_run,
.cleanup = &pay_cleanup,
.traits = &pay_traits
};
return cmd;
}
}
struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_pay_order (const char *label,
const char *merchant_url,
unsigned int http_status,
const char *proposal_reference,
const char *coin_reference,
const char *amount_with_fee,
const char *amount_without_fee,
const char *session_id)
{
return TALER_TESTING_cmd_merchant_pay_order_choices (label,
merchant_url,
http_status,
proposal_reference,
coin_reference,
amount_with_fee,
amount_without_fee,
session_id,
-1,
NULL);
}
/* end of testing_api_cmd_pay_order.c */