/*
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.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"
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, // which
irefund_amount, // how much
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 */