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