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