/* 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); } /** * Process "GET /public/refund" (lookup) response; * mainly checking if the refunded amount matches the * expectation. * * @param cls closure * @param hr HTTP response we got * @param h_contract_terms hash of the contract terms to which the refund is applied * @param merchant_pub public key of the merchant * @param num_details length of the @a details array * @param details details about the refund processing */ static void refund_lookup_cb (void *cls, const struct TALER_MERCHANT_HttpResponse *hr, const struct GNUNET_HashCode *h_contract_terms, const struct TALER_MerchantPublicKeyP *merchant_pub, unsigned int num_details, const struct TALER_MERCHANT_RefundDetail *details) { struct RefundLookupState *rls = cls; struct GNUNET_CONTAINER_MultiHashMap *map; const char *coin_reference; const char *icoin_reference; const char *refund_amount; struct TALER_Amount acc; struct TALER_Amount ra; rls->rlo = NULL; if (MHD_HTTP_GONE == rls->http_code) { /* special case: GONE is not the top-level code, but expected INSIDE the details */ if (MHD_HTTP_OK != hr->http_status) TALER_TESTING_FAIL (rls->is); for (unsigned int i = 0; iis); /* all good */ TALER_TESTING_interpreter_next (rls->is); return; } if (rls->http_code != hr->http_status) TALER_TESTING_FAIL (rls->is); if (MHD_HTTP_OK != hr->http_status) { TALER_TESTING_interpreter_next (rls->is); return; } map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); /* Put in array every refunded coin. */ for (unsigned int i = 0; iis); return; } TALER_LOG_DEBUG ("Coin %s refund is %s\n", TALER_B2S (&details[i].coin_pub), TALER_amount2s (&details[i].refund_amount)); GNUNET_CRYPTO_hash (&details[i].coin_pub, sizeof (struct TALER_CoinSpendPublicKeyP), &h_coin_pub); if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put ( map, &h_coin_pub, (void *) &details[i], GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) { GNUNET_CONTAINER_multihashmap_destroy (map); TALER_TESTING_FAIL (rls->is); } } /* Compare spent coins with refunded, and if they match, * increase an accumulator. */ { const struct TALER_TESTING_Command *pay_cmd; if (NULL == (pay_cmd = TALER_TESTING_interpreter_lookup_command ( rls->is, rls->pay_reference))) { GNUNET_CONTAINER_multihashmap_destroy (map); TALER_TESTING_FAIL (rls->is); } if (GNUNET_OK != TALER_TESTING_get_trait_coin_reference ( pay_cmd, 0, &coin_reference)) { GNUNET_CONTAINER_multihashmap_destroy (map); TALER_TESTING_FAIL (rls->is); } } GNUNET_assert (GNUNET_OK == TALER_amount_get_zero ("EUR", &acc)); { char *coin_reference_dup; 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; const struct TALER_MERCHANT_RefundDetail *idetail; 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); TALER_LOG_DEBUG ("Looking at coin %s\n", TALER_B2S (&icoin_pub)); GNUNET_CRYPTO_hash (&icoin_pub, sizeof (struct TALER_CoinSpendPublicKeyP), &h_icoin_pub); idetail = GNUNET_CONTAINER_multihashmap_get (map, &h_icoin_pub); /* Can be NULL: not all coins are involved in refund */ if (NULL == idetail) continue; TALER_LOG_DEBUG ("Found coin %s refund of %s\n", TALER_B2S (&idetail->coin_pub), TALER_amount2s (&idetail->refund_amount)); GNUNET_assert (0 <= TALER_amount_add (&acc, &acc, &idetail->refund_amount)); } GNUNET_free (coin_reference_dup); } { const struct TALER_TESTING_Command *increase_cmd; 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_destroy (map); /* Check that what the backend claims to have been refunded * actually matches _our_ refund expectation. */ if (0 != TALER_amount_cmp (&acc, &ra)) { char *a1; a1 = TALER_amount_to_string (&ra); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Incomplete refund: expected '%s', got '%s'\n", a1, TALER_amount2s (&acc)); GNUNET_free (a1); 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 */