testing_api_cmd_wallet_post_orders_refund.c (8836B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020-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 src/testing/testing_api_cmd_wallet_post_orders_refund.c 21 * @brief command to test refunds. 22 * @author Marcello Stanisci 23 * @author Christian Grothoff 24 */ 25 #include "platform.h" 26 struct WalletRefundState; 27 #define TALER_MERCHANT_POST_ORDERS_REFUND_RESULT_CLOSURE struct WalletRefundState 28 #include <taler/taler_exchange_service.h> 29 #include <taler/taler_testing_lib.h> 30 #include "taler/taler_merchant_service.h" 31 #include "taler/taler_merchant_testing_lib.h" 32 #include <taler/merchant/post-orders-ORDER_ID-refund.h> 33 34 35 /** 36 * State for an "obtain refunds" CMD. 37 */ 38 struct WalletRefundState 39 { 40 /** 41 * Operation handle for a (public) POST /orders/$ID/refund request. 42 */ 43 struct TALER_MERCHANT_PostOrdersRefundHandle *orh; 44 45 /** 46 * Base URL of the merchant serving the request. 47 */ 48 const char *merchant_url; 49 50 /** 51 * Interpreter state. 52 */ 53 struct TALER_TESTING_Interpreter *is; 54 55 /** 56 * Expected HTTP response code. 57 */ 58 unsigned int http_code; 59 60 /** 61 * Label of the command that created the order we want to obtain refunds for. 62 */ 63 const char *proposal_reference; 64 65 /** 66 * A list of refunds associated with this order. 67 */ 68 const char **refunds; 69 70 /** 71 * The length of @e refunds. 72 */ 73 unsigned int refunds_length; 74 }; 75 76 77 /** 78 * Process POST /refund (increase) response; just checking 79 * if the HTTP response code is the one expected. 80 * 81 * @param cls closure 82 * @param wrr response 83 */ 84 static void 85 refund_cb ( 86 struct WalletRefundState *wrs, 87 const struct TALER_MERCHANT_PostOrdersRefundResponse *wrr) 88 { 89 90 wrs->orh = NULL; 91 if (wrs->http_code != wrr->hr.http_status) 92 { 93 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 94 "Expected status %u, got %u(%d) for refund increase\n", 95 wrs->http_code, 96 wrr->hr.http_status, 97 (int) wrr->hr.ec); 98 TALER_TESTING_FAIL (wrs->is); 99 } 100 switch (wrr->hr.http_status) 101 { 102 case MHD_HTTP_OK: 103 { 104 struct TALER_Amount refunded_total; 105 if (wrr->details.ok.num_refunds != wrs->refunds_length) 106 { 107 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 108 "Number of refunds found (%u) does not match expected (%u)\n", 109 wrr->details.ok.num_refunds, 110 wrs->refunds_length); 111 TALER_TESTING_interpreter_fail (wrs->is); 112 return; 113 } 114 if (wrr->details.ok.num_refunds > 0) 115 GNUNET_assert (GNUNET_OK == 116 TALER_amount_set_zero ( 117 wrr->details.ok.refunds[0].refund_amount.currency, 118 &refunded_total)); 119 for (unsigned int i = 0; i < wrr->details.ok.num_refunds; ++i) 120 { 121 const struct TALER_MERCHANT_PostOrdersRefundDetail *refund 122 = &wrr->details.ok.refunds[wrr->details.ok.num_refunds - 1 - i]; 123 const struct TALER_TESTING_Command *refund_cmd; 124 const struct TALER_Amount *expected_amount; 125 126 refund_cmd = TALER_TESTING_interpreter_lookup_command ( 127 wrs->is, 128 wrs->refunds[i]); 129 130 if (GNUNET_OK != 131 TALER_TESTING_get_trait_amount (refund_cmd, 132 &expected_amount)) 133 { 134 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 135 "Could not fetch refund amount\n"); 136 TALER_TESTING_interpreter_fail (wrs->is); 137 return; 138 } 139 /* The most recent refunds are returned first */ 140 GNUNET_assert (0 <= TALER_amount_add (&refunded_total, 141 &refunded_total, 142 &refund->refund_amount)); 143 if ( (GNUNET_OK != 144 TALER_amount_cmp_currency (expected_amount, 145 &refunded_total)) || 146 (0 != TALER_amount_cmp (expected_amount, 147 &refunded_total)) ) 148 { 149 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 150 "Refund amounts do not match\n"); 151 TALER_TESTING_interpreter_fail (wrs->is); 152 return; 153 } 154 } 155 } 156 break; 157 default: 158 break; 159 } 160 TALER_TESTING_interpreter_next (wrs->is); 161 } 162 163 164 /** 165 * Run the "refund increase" CMD. 166 * 167 * @param cls closure. 168 * @param cmd command currently being run. 169 * @param is the interpreter state. 170 */ 171 static void 172 obtain_refunds_run (void *cls, 173 const struct TALER_TESTING_Command *cmd, 174 struct TALER_TESTING_Interpreter *is) 175 { 176 struct WalletRefundState *wrs = cls; 177 const struct TALER_TESTING_Command *proposal_cmd = 178 TALER_TESTING_interpreter_lookup_command (is, 179 wrs->proposal_reference); 180 const struct TALER_PrivateContractHashP *h_contract_terms; 181 const char *order_id; 182 183 if (NULL == proposal_cmd) 184 TALER_TESTING_FAIL (is); 185 if (GNUNET_OK != 186 TALER_TESTING_get_trait_h_contract_terms (proposal_cmd, 187 &h_contract_terms)) 188 TALER_TESTING_FAIL (is); 189 190 { 191 const json_t *contract_terms; 192 const char *error_name; 193 unsigned int error_line; 194 195 if (GNUNET_OK != 196 TALER_TESTING_get_trait_contract_terms (proposal_cmd, 197 &contract_terms)) 198 TALER_TESTING_FAIL (is); 199 { 200 /* Get information that needs to be put verbatim in the 201 * deposit permission */ 202 struct GNUNET_JSON_Specification spec[] = { 203 GNUNET_JSON_spec_string ("order_id", 204 &order_id), 205 GNUNET_JSON_spec_end () 206 }; 207 208 if (GNUNET_OK != 209 GNUNET_JSON_parse (contract_terms, 210 spec, 211 &error_name, 212 &error_line)) 213 { 214 char *js; 215 216 js = json_dumps (contract_terms, 217 JSON_INDENT (1)); 218 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 219 "Parser failed on %s:%u for input `%s'\n", 220 error_name, 221 error_line, 222 js); 223 free (js); 224 TALER_TESTING_FAIL (is); 225 } 226 } 227 } 228 229 wrs->is = is; 230 wrs->orh = TALER_MERCHANT_post_orders_refund_create ( 231 TALER_TESTING_interpreter_get_context (is), 232 wrs->merchant_url, 233 order_id, 234 h_contract_terms); 235 { 236 enum TALER_ErrorCode ec; 237 238 ec = TALER_MERCHANT_post_orders_refund_start ( 239 wrs->orh, 240 &refund_cb, 241 wrs); 242 GNUNET_assert (TALER_EC_NONE == ec); 243 } 244 } 245 246 247 /** 248 * Free the state of a "refund increase" CMD, and 249 * possibly cancel a pending "refund increase" operation. 250 * 251 * @param cls closure 252 * @param cmd command currently being freed. 253 */ 254 static void 255 obtain_refunds_cleanup (void *cls, 256 const struct TALER_TESTING_Command *cmd) 257 { 258 struct WalletRefundState *wrs = cls; 259 260 if (NULL != wrs->orh) 261 { 262 TALER_LOG_WARNING ("Refund operation did not complete\n"); 263 TALER_MERCHANT_post_orders_refund_cancel (wrs->orh); 264 } 265 GNUNET_array_grow (wrs->refunds, 266 wrs->refunds_length, 267 0); 268 GNUNET_free (wrs); 269 } 270 271 272 struct TALER_TESTING_Command 273 TALER_TESTING_cmd_wallet_order_refund (const char *label, 274 const char *merchant_url, 275 const char *order_ref, 276 unsigned int http_code, 277 ...) 278 { 279 struct WalletRefundState *wrs; 280 281 wrs = GNUNET_new (struct WalletRefundState); 282 wrs->merchant_url = merchant_url; 283 wrs->proposal_reference = order_ref; 284 wrs->http_code = http_code; 285 wrs->refunds_length = 0; 286 { 287 const char *clabel; 288 va_list ap; 289 290 va_start (ap, http_code); 291 while (NULL != (clabel = va_arg (ap, const char *))) 292 { 293 GNUNET_array_append (wrs->refunds, 294 wrs->refunds_length, 295 clabel); 296 } 297 va_end (ap); 298 } 299 { 300 struct TALER_TESTING_Command cmd = { 301 .cls = wrs, 302 .label = label, 303 .run = &obtain_refunds_run, 304 .cleanup = &obtain_refunds_cleanup 305 }; 306 307 return cmd; 308 } 309 }