/*
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
*/
/**
* @file lib/testing_api_cmd_refund_lookup.c
* @brief command to test refunds.
* @author Marcello Stanisci
*/
#include "platform.h"
#include
#include
#include "taler_merchant_service.h"
#include "taler_merchant_testing_lib.h"
/**
* State for a "refund lookup" CMD.
*/
struct RefundLookupState
{
/**
* Operation handle for a GET /public/refund request.
*/
struct TALER_MERCHANT_RefundLookupOperation *rlo;
/**
* Base URL of the merchant serving the request.
*/
const char *merchant_url;
/**
* Order id to look up.
*/
const char *order_id;
/**
* Reference to a "pay" CMD, used to double-check if
* refunded coins were actually spent:
*/
const char *pay_reference;
/**
* Reference to a "refund increase" CMD that offer
* the expected amount to be refunded; can be NULL.
*/
const char *increase_reference;
/**
* Expected HTTP response code.
*/
unsigned int http_code;
/**
* Interpreter state.
*/
struct TALER_TESTING_Interpreter *is;
/**
* Explicit amount to be refunded, must be defined if @a
* increase_reference is NULL.
*/
const char *refund_amount;
};
/**
* Free the state of a "refund lookup" CMD, and
* possibly cancel a pending "refund lookup" operation.
*
* @param cls closure
* @param cmd command currently being freed.
*/
static void
refund_lookup_cleanup (void *cls,
const struct TALER_TESTING_Command *cmd)
{
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);
}
/**
* 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 /public/refund" (lookup) response;
* mainly checking if the refunded amount matches the
* expectation.
*
* @param cls closure
* @param hr HTTP response we got
*/
static void
refund_lookup_cb (void *cls,
const struct TALER_MERCHANT_HttpResponse *hr)
{
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 *increase_cmd;
const char *refund_amount;
struct TALER_Amount acc;
struct TALER_Amount ra;
const json_t *arr;
rls->rlo = NULL;
if (rls->http_code != hr->http_status)
TALER_TESTING_FAIL (rls->is);
arr = json_object_get (hr->reply,
"refund_permissions");
if (NULL == arr)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Tolerating a refund permission not found\n");
TALER_TESTING_interpreter_next (rls->is);
return;
}
map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO);
/* Put in array every refunded coin. */
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, // which
irefund_amount, // how much
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
};
/* Compare spent coins with refunded, and if they match,
* increase an accumulator. */
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, ";"))
{
const struct TALER_CoinSpendPrivateKeyP *icoin_priv;
struct TALER_CoinSpendPublicKeyP icoin_pub;
struct GNUNET_HashCode h_icoin_pub;
struct TALER_Amount *iamount;
const struct TALER_TESTING_Command *icoin_cmd;
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);
GNUNET_CONTAINER_multihashmap_destroy (map);
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);
GNUNET_CONTAINER_multihashmap_destroy (map);
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)))
{
if (GNUNET_OK !=
TALER_TESTING_get_trait_string (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);
}
else
{
GNUNET_assert (NULL != rls->refund_amount);
if (GNUNET_OK !=
TALER_string_to_amount (rls->refund_amount,
&ra))
TALER_TESTING_FAIL (rls->is);
}
GNUNET_CONTAINER_multihashmap_iterate (map,
&hashmap_free,
NULL);
GNUNET_CONTAINER_multihashmap_destroy (map);
/* Check that what the backend claims to have been refunded
* actually matches _our_ refund expectation. */
if (0 != TALER_amount_cmp (&acc,
&ra))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Incomplete refund: expected '%s', got '%s'\n",
TALER_amount_to_string (&ra),
TALER_amount_to_string (&acc));
TALER_TESTING_interpreter_fail (rls->is);
return;
}
TALER_TESTING_interpreter_next (rls->is);
}
/**
* Run the "refund lookup" CMD.
*
* @param cls closure.
* @param cmd command being currently run.
* @param is interpreter state.
*/
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 (is->ctx,
rls->merchant_url,
rls->order_id,
&refund_lookup_cb,
rls);
GNUNET_assert (NULL != rls->rlo);
}
/**
* Define a "refund lookup" CMD.
*
* @param label command label.
* @param merchant_url base URL of the merchant serving the
* "refund lookup" request.
* @param increase_reference reference to a "refund increase" CMD
* that will offer the amount to check the looked up refund
* against. Must NOT be NULL.
* @param pay_reference reference to the "pay" CMD whose coins got
* refunded. It is used to double-check if the refunded
* coins were actually spent in the first place.
* @param order_id order id whose refund status is to be looked up.
* @param http_code expected HTTP response code.
*
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_refund_lookup (
const char *label,
const char *merchant_url,
const char *increase_reference,
const char *pay_reference,
const char *order_id,
unsigned int http_code)
{
struct RefundLookupState *rls;
rls = GNUNET_new (struct RefundLookupState);
rls->merchant_url = merchant_url;
rls->order_id = order_id;
rls->pay_reference = pay_reference;
rls->increase_reference = increase_reference;
rls->http_code = http_code;
{
struct TALER_TESTING_Command cmd = {
.cls = rls,
.label = label,
.run = &refund_lookup_run,
.cleanup = &refund_lookup_cleanup
};
return cmd;
}
}
/**
* Define a "refund lookup" CMD, equipped with a expected refund
* amount.
*
* @param label command label.
* @param merchant_url base URL of the merchant serving the
* "refund lookup" request.
* @param increase_reference reference to a "refund increase" CMD
* that will offer the amount to check the looked up refund
* against. Can be NULL, takes precedence over @a
* refund_amount.
* @param pay_reference reference to the "pay" CMD whose coins got
* refunded. It is used to double-check if the refunded
* coins were actually spent in the first place.
* @param order_id order id whose refund status is to be looked up.
* @param http_code expected HTTP response code.
* @param refund_amount expected refund amount. Must be defined
* if @a increase_reference is NULL.
*
* @return the command.
*/
struct TALER_TESTING_Command
TALER_TESTING_cmd_refund_lookup_with_amount (
const char *label,
const char *merchant_url,
const char *increase_reference,
const char *pay_reference,
const char *order_id,
unsigned int http_code,
const char *refund_amount)
{
struct RefundLookupState *rls;
rls = GNUNET_new (struct RefundLookupState);
rls->merchant_url = merchant_url;
rls->order_id = order_id;
rls->pay_reference = pay_reference;
rls->increase_reference = increase_reference;
rls->http_code = http_code;
rls->refund_amount = refund_amount;
{
struct TALER_TESTING_Command cmd = {
.cls = rls,
.label = label,
.run = &refund_lookup_run,
.cleanup = &refund_lookup_cleanup
};
return cmd;
}
}
/* end of testing_api_cmd_refund_lookup.c */