/* This file is part of TALER Copyright (C) 2014-2023 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/testing_api_cmd_refund.c * @brief Implement the /refund test command, plus other * corollary commands (?). * @author Marcello Stanisci */ #include "platform.h" #include "taler_json_lib.h" #include #include "taler_testing_lib.h" /** * State for a "refund" CMD. */ struct RefundState { /** * Expected HTTP response code. */ unsigned int expected_response_code; /** * Amount to be refunded. */ const char *refund_amount; /** * Reference to any command that can provide a coin to refund. */ const char *coin_reference; /** * Refund transaction identifier. */ uint64_t refund_transaction_id; /** * Entry in the coin's history generated by this operation. */ struct TALER_EXCHANGE_CoinHistoryEntry che; /** * Public key of the refunded coin. */ struct TALER_CoinSpendPublicKeyP coin; /** * Handle to the refund operation. */ struct TALER_EXCHANGE_RefundHandle *rh; /** * Interpreter state. */ struct TALER_TESTING_Interpreter *is; }; /** * Check the result for the refund request, just check if the * response code is acceptable. * * @param cls closure * @param rr response details */ static void refund_cb (void *cls, const struct TALER_EXCHANGE_RefundResponse *rr) { struct RefundState *rs = cls; const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr; rs->rh = NULL; if (rs->expected_response_code != hr->http_status) { TALER_TESTING_unexpected_status (rs->is, hr->http_status, rs->expected_response_code); return; } if (MHD_HTTP_OK == hr->http_status) { struct TALER_Amount refund_amount; if (GNUNET_OK != TALER_string_to_amount (rs->refund_amount, &refund_amount)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to parse amount `%s'\n", rs->refund_amount); TALER_TESTING_interpreter_fail (rs->is); return; } if (0 > TALER_amount_subtract (&rs->che.amount, &refund_amount, &rs->che.details.refund.refund_fee)) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to subtract %s from %s\n", TALER_amount2s (&rs->che.details.refund.refund_fee), rs->refund_amount); TALER_TESTING_interpreter_fail (rs->is); return; } } TALER_TESTING_interpreter_next (rs->is); } /** * Run the command. * * @param cls closure. * @param cmd the command to execute. * @param is the interpreter state. */ static void refund_run (void *cls, const struct TALER_TESTING_Command *cmd, struct TALER_TESTING_Interpreter *is) { struct RefundState *rs = cls; const struct TALER_CoinSpendPrivateKeyP *coin_priv; const json_t *contract_terms; struct TALER_PrivateContractHashP h_contract_terms; struct TALER_Amount refund_amount; const struct TALER_MerchantPrivateKeyP *merchant_priv; const struct TALER_TESTING_Command *coin_cmd; const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; rs->is = is; if (GNUNET_OK != TALER_string_to_amount (rs->refund_amount, &refund_amount)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to parse amount `%s' at %s\n", rs->refund_amount, cmd->label); TALER_TESTING_interpreter_fail (is); return; } coin_cmd = TALER_TESTING_interpreter_lookup_command (is, rs->coin_reference); if (NULL == coin_cmd) { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); return; } if (GNUNET_OK != TALER_TESTING_get_trait_contract_terms (coin_cmd, &contract_terms)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); return; } GNUNET_assert (GNUNET_OK == TALER_JSON_contract_hash (contract_terms, &h_contract_terms)); /* Hunting for a coin .. */ if ( (GNUNET_OK != TALER_TESTING_get_trait_coin_priv (coin_cmd, 0, &coin_priv)) || (GNUNET_OK != TALER_TESTING_get_trait_denom_pub (coin_cmd, 0, &denom_pub)) ) { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); return; } GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, &rs->coin.eddsa_pub); if (GNUNET_OK != TALER_TESTING_get_trait_merchant_priv (coin_cmd, &merchant_priv)) { GNUNET_break (0); TALER_TESTING_interpreter_fail (is); return; } rs->che.type = TALER_EXCHANGE_CTT_REFUND; rs->che.details.refund.h_contract_terms = h_contract_terms; GNUNET_CRYPTO_eddsa_key_get_public ( &merchant_priv->eddsa_priv, &rs->che.details.refund.merchant_pub.eddsa_pub); rs->che.details.refund.refund_fee = denom_pub->fees.refund; rs->che.details.refund.sig_amount = refund_amount; rs->che.details.refund.rtransaction_id = rs->refund_transaction_id; TALER_merchant_refund_sign (&rs->coin, &h_contract_terms, rs->refund_transaction_id, &refund_amount, merchant_priv, &rs->che.details.refund.sig); rs->rh = TALER_EXCHANGE_refund ( TALER_TESTING_interpreter_get_context (is), TALER_TESTING_get_exchange_url (is), TALER_TESTING_get_keys (is), &refund_amount, &h_contract_terms, &rs->coin, rs->refund_transaction_id, merchant_priv, &refund_cb, rs); GNUNET_assert (NULL != rs->rh); } /** * Offer internal data from a "refund" CMD, to other commands. * * @param cls closure. * @param[out] ret result. * @param trait name of the trait. * @param index index number of the object to offer. * @return #GNUNET_OK on success. */ static enum GNUNET_GenericReturnValue refund_traits (void *cls, const void **ret, const char *trait, unsigned int index) { struct RefundState *rs = cls; struct TALER_TESTING_Trait traits[] = { TALER_TESTING_make_trait_coin_history (0, &rs->che), TALER_TESTING_make_trait_coin_pub (0, &rs->coin), TALER_TESTING_trait_end () }; return TALER_TESTING_get_trait (traits, ret, trait, index); } /** * Free the state from a "refund" CMD, and possibly cancel * a pending operation thereof. * * @param cls closure. * @param cmd the command which is being cleaned up. */ static void refund_cleanup (void *cls, const struct TALER_TESTING_Command *cmd) { struct RefundState *rs = cls; if (NULL != rs->rh) { TALER_TESTING_command_incomplete (rs->is, cmd->label); TALER_EXCHANGE_refund_cancel (rs->rh); rs->rh = NULL; } GNUNET_free (rs); } struct TALER_TESTING_Command TALER_TESTING_cmd_refund (const char *label, unsigned int expected_response_code, const char *refund_amount, const char *coin_reference) { struct RefundState *rs; rs = GNUNET_new (struct RefundState); rs->expected_response_code = expected_response_code; rs->refund_amount = refund_amount; rs->coin_reference = coin_reference; { struct TALER_TESTING_Command cmd = { .cls = rs, .label = label, .run = &refund_run, .cleanup = &refund_cleanup, .traits = &refund_traits }; return cmd; } } struct TALER_TESTING_Command TALER_TESTING_cmd_refund_with_id ( const char *label, unsigned int expected_response_code, const char *refund_amount, const char *coin_reference, uint64_t refund_transaction_id) { struct RefundState *rs; rs = GNUNET_new (struct RefundState); rs->expected_response_code = expected_response_code; rs->refund_amount = refund_amount; rs->coin_reference = coin_reference; rs->refund_transaction_id = refund_transaction_id; { struct TALER_TESTING_Command cmd = { .cls = rs, .label = label, .run = &refund_run, .cleanup = &refund_cleanup, .traits = &refund_traits }; return cmd; } }