testing_api_cmd_refund.c (9883B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as 7 published by the Free Software Foundation; either version 3, or 8 (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, see 17 <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file testing/testing_api_cmd_refund.c 21 * @brief Implement the /refund test command, plus other 22 * corollary commands (?). 23 * @author Marcello Stanisci 24 */ 25 #include "taler/taler_json_lib.h" 26 #include <gnunet/gnunet_curl_lib.h> 27 struct RefundState; 28 #define TALER_EXCHANGE_POST_COINS_REFUND_RESULT_CLOSURE struct RefundState 29 #include "taler/exchange/post-coins-COIN_PUB-refund.h" 30 #include "taler/taler_testing_lib.h" 31 32 33 /** 34 * State for a "refund" CMD. 35 */ 36 struct RefundState 37 { 38 /** 39 * Expected HTTP response code. 40 */ 41 unsigned int expected_response_code; 42 43 /** 44 * Amount to be refunded. 45 */ 46 const char *refund_amount; 47 48 /** 49 * Reference to any command that can provide a coin to refund. 50 */ 51 const char *coin_reference; 52 53 /** 54 * Refund transaction identifier. 55 */ 56 uint64_t refund_transaction_id; 57 58 /** 59 * Entry in the coin's history generated by this operation. 60 */ 61 struct TALER_EXCHANGE_CoinHistoryEntry che; 62 63 /** 64 * Public key of the refunded coin. 65 */ 66 struct TALER_CoinSpendPublicKeyP coin; 67 68 /** 69 * Handle to the refund operation. 70 */ 71 struct TALER_EXCHANGE_PostCoinsRefundHandle *rh; 72 73 /** 74 * Interpreter state. 75 */ 76 struct TALER_TESTING_Interpreter *is; 77 }; 78 79 80 /** 81 * Check the result for the refund request, just check if the 82 * response code is acceptable. 83 * 84 * @param rs closure 85 * @param rr response details 86 */ 87 static void 88 refund_cb (struct RefundState *rs, 89 const struct TALER_EXCHANGE_PostCoinsRefundResponse *rr) 90 { 91 const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr; 92 93 rs->rh = NULL; 94 if (rs->expected_response_code != hr->http_status) 95 { 96 TALER_TESTING_unexpected_status (rs->is, 97 hr->http_status, 98 rs->expected_response_code); 99 return; 100 } 101 if (MHD_HTTP_OK == hr->http_status) 102 { 103 struct TALER_Amount refund_amount; 104 105 if (GNUNET_OK != 106 TALER_string_to_amount (rs->refund_amount, 107 &refund_amount)) 108 { 109 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 110 "Failed to parse amount `%s'\n", 111 rs->refund_amount); 112 TALER_TESTING_interpreter_fail (rs->is); 113 return; 114 } 115 if (0 > 116 TALER_amount_subtract (&rs->che.amount, 117 &refund_amount, 118 &rs->che.details.refund.refund_fee)) 119 { 120 GNUNET_break (0); 121 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 122 "Failed to subtract %s from %s\n", 123 TALER_amount2s (&rs->che.details.refund.refund_fee), 124 rs->refund_amount); 125 TALER_TESTING_interpreter_fail (rs->is); 126 return; 127 } 128 } 129 TALER_TESTING_interpreter_next (rs->is); 130 } 131 132 133 /** 134 * Run the command. 135 * 136 * @param cls closure. 137 * @param cmd the command to execute. 138 * @param is the interpreter state. 139 */ 140 static void 141 refund_run (void *cls, 142 const struct TALER_TESTING_Command *cmd, 143 struct TALER_TESTING_Interpreter *is) 144 { 145 struct RefundState *rs = cls; 146 const struct TALER_CoinSpendPrivateKeyP *coin_priv; 147 const json_t *contract_terms; 148 struct TALER_PrivateContractHashP h_contract_terms; 149 struct TALER_Amount refund_amount; 150 const struct TALER_MerchantPrivateKeyP *merchant_priv; 151 const struct TALER_TESTING_Command *coin_cmd; 152 const struct TALER_EXCHANGE_DenomPublicKey *denom_pub; 153 154 rs->is = is; 155 if (GNUNET_OK != 156 TALER_string_to_amount (rs->refund_amount, 157 &refund_amount)) 158 { 159 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 160 "Failed to parse amount `%s' at %s\n", 161 rs->refund_amount, 162 cmd->label); 163 TALER_TESTING_interpreter_fail (is); 164 return; 165 } 166 coin_cmd = TALER_TESTING_interpreter_lookup_command (is, 167 rs->coin_reference); 168 if (NULL == coin_cmd) 169 { 170 GNUNET_break (0); 171 TALER_TESTING_interpreter_fail (is); 172 return; 173 } 174 if (GNUNET_OK != 175 TALER_TESTING_get_trait_contract_terms (coin_cmd, 176 &contract_terms)) 177 { 178 GNUNET_break (0); 179 TALER_TESTING_interpreter_fail (is); 180 return; 181 } 182 GNUNET_assert (GNUNET_OK == 183 TALER_JSON_contract_hash (contract_terms, 184 &h_contract_terms)); 185 186 /* Hunting for a coin .. */ 187 if ( (GNUNET_OK != 188 TALER_TESTING_get_trait_coin_priv (coin_cmd, 189 0, 190 &coin_priv)) || 191 (GNUNET_OK != 192 TALER_TESTING_get_trait_denom_pub (coin_cmd, 193 0, 194 &denom_pub)) ) 195 196 { 197 GNUNET_break (0); 198 TALER_TESTING_interpreter_fail (is); 199 return; 200 } 201 202 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, 203 &rs->coin.eddsa_pub); 204 if (GNUNET_OK != 205 TALER_TESTING_get_trait_merchant_priv (coin_cmd, 206 &merchant_priv)) 207 { 208 GNUNET_break (0); 209 TALER_TESTING_interpreter_fail (is); 210 return; 211 } 212 rs->che.type = TALER_EXCHANGE_CTT_REFUND; 213 rs->che.details.refund.h_contract_terms = h_contract_terms; 214 GNUNET_CRYPTO_eddsa_key_get_public ( 215 &merchant_priv->eddsa_priv, 216 &rs->che.details.refund.merchant_pub.eddsa_pub); 217 rs->che.details.refund.refund_fee = denom_pub->fees.refund; 218 rs->che.details.refund.sig_amount = refund_amount; 219 rs->che.details.refund.rtransaction_id = rs->refund_transaction_id; 220 TALER_merchant_refund_sign (&rs->coin, 221 &h_contract_terms, 222 rs->refund_transaction_id, 223 &refund_amount, 224 merchant_priv, 225 &rs->che.details.refund.sig); 226 rs->rh = TALER_EXCHANGE_post_coins_refund_create ( 227 TALER_TESTING_interpreter_get_context (is), 228 TALER_TESTING_get_exchange_url (is), 229 TALER_TESTING_get_keys (is), 230 &refund_amount, 231 &h_contract_terms, 232 &rs->coin, 233 rs->refund_transaction_id, 234 merchant_priv); 235 GNUNET_assert (NULL != rs->rh); 236 GNUNET_assert (TALER_EC_NONE == 237 TALER_EXCHANGE_post_coins_refund_start (rs->rh, 238 &refund_cb, 239 rs)); 240 } 241 242 243 /** 244 * Offer internal data from a "refund" CMD, to other commands. 245 * 246 * @param cls closure. 247 * @param[out] ret result. 248 * @param trait name of the trait. 249 * @param index index number of the object to offer. 250 * @return #GNUNET_OK on success. 251 */ 252 static enum GNUNET_GenericReturnValue 253 refund_traits (void *cls, 254 const void **ret, 255 const char *trait, 256 unsigned int index) 257 { 258 struct RefundState *rs = cls; 259 struct TALER_TESTING_Trait traits[] = { 260 TALER_TESTING_make_trait_coin_history (0, 261 &rs->che), 262 TALER_TESTING_make_trait_coin_pub (0, 263 &rs->coin), 264 TALER_TESTING_trait_end () 265 }; 266 267 return TALER_TESTING_get_trait (traits, 268 ret, 269 trait, 270 index); 271 } 272 273 274 /** 275 * Free the state from a "refund" CMD, and possibly cancel 276 * a pending operation thereof. 277 * 278 * @param cls closure. 279 * @param cmd the command which is being cleaned up. 280 */ 281 static void 282 refund_cleanup (void *cls, 283 const struct TALER_TESTING_Command *cmd) 284 { 285 struct RefundState *rs = cls; 286 287 if (NULL != rs->rh) 288 { 289 TALER_TESTING_command_incomplete (rs->is, 290 cmd->label); 291 TALER_EXCHANGE_post_coins_refund_cancel (rs->rh); 292 rs->rh = NULL; 293 } 294 GNUNET_free (rs); 295 } 296 297 298 struct TALER_TESTING_Command 299 TALER_TESTING_cmd_refund (const char *label, 300 unsigned int expected_response_code, 301 const char *refund_amount, 302 const char *coin_reference) 303 { 304 struct RefundState *rs; 305 306 rs = GNUNET_new (struct RefundState); 307 rs->expected_response_code = expected_response_code; 308 rs->refund_amount = refund_amount; 309 rs->coin_reference = coin_reference; 310 { 311 struct TALER_TESTING_Command cmd = { 312 .cls = rs, 313 .label = label, 314 .run = &refund_run, 315 .cleanup = &refund_cleanup, 316 .traits = &refund_traits 317 }; 318 319 return cmd; 320 } 321 } 322 323 324 struct TALER_TESTING_Command 325 TALER_TESTING_cmd_refund_with_id ( 326 const char *label, 327 unsigned int expected_response_code, 328 const char *refund_amount, 329 const char *coin_reference, 330 uint64_t refund_transaction_id) 331 { 332 struct RefundState *rs; 333 334 rs = GNUNET_new (struct RefundState); 335 rs->expected_response_code = expected_response_code; 336 rs->refund_amount = refund_amount; 337 rs->coin_reference = coin_reference; 338 rs->refund_transaction_id = refund_transaction_id; 339 { 340 struct TALER_TESTING_Command cmd = { 341 .cls = rs, 342 .label = label, 343 .run = &refund_run, 344 .cleanup = &refund_cleanup, 345 .traits = &refund_traits 346 }; 347 348 return cmd; 349 } 350 }