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