merchant_api_post_order_abort.c (12531B)
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 Lesser General Public License as 7 published by the Free Software Foundation; either version 2.1, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General 16 Public License along with TALER; see the file COPYING.LGPL. 17 If not, see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file merchant_api_post_order_abort.c 21 * @brief Implementation of the POST /orders/$ID/abort request 22 * of the merchant's HTTP API 23 * @author Christian Grothoff 24 * @author Marcello Stanisci 25 */ 26 #include "platform.h" 27 #include <curl/curl.h> 28 #include <jansson.h> 29 #include <microhttpd.h> /* just for HTTP status codes */ 30 #include <gnunet/gnunet_util_lib.h> 31 #include <gnunet/gnunet_curl_lib.h> 32 #include "taler_merchant_service.h" 33 #include "merchant_api_curl_defaults.h" 34 #include "merchant_api_common.h" 35 #include <taler/taler_json_lib.h> 36 #include <taler/taler_signatures.h> 37 #include <taler/taler_exchange_service.h> 38 #include <taler/taler_curl_lib.h> 39 40 41 /** 42 * Maximum number of refunds we return. 43 */ 44 #define MAX_REFUNDS 1024 45 46 47 /** 48 * @brief An abort Handle 49 */ 50 struct TALER_MERCHANT_OrderAbortHandle 51 { 52 /** 53 * Hash of the contract. 54 */ 55 struct TALER_PrivateContractHashP h_contract_terms; 56 57 /** 58 * Public key of the merchant. 59 */ 60 struct TALER_MerchantPublicKeyP merchant_pub; 61 62 /** 63 * The url for this request. 64 */ 65 char *url; 66 67 /** 68 * Handle for the request. 69 */ 70 struct GNUNET_CURL_Job *job; 71 72 /** 73 * Function to call with the result. 74 */ 75 TALER_MERCHANT_AbortCallback abort_cb; 76 77 /** 78 * Closure for @a abort_cb. 79 */ 80 void *abort_cb_cls; 81 82 /** 83 * Reference to the execution context. 84 */ 85 struct GNUNET_CURL_Context *ctx; 86 87 /** 88 * Minor context that holds body and headers. 89 */ 90 struct TALER_CURL_PostContext post_ctx; 91 92 /** 93 * The coins we are aborting on. 94 */ 95 struct TALER_MERCHANT_AbortCoin *coins; 96 97 /** 98 * Number of @e coins we are paying with. 99 */ 100 unsigned int num_coins; 101 102 }; 103 104 105 /** 106 * Check that the response for an abort is well-formed, 107 * and call the application callback with the result if it is 108 * OK. Otherwise returns #GNUNET_SYSERR. 109 * 110 * @param oah handle to operation that created the reply 111 * @param[in] ar abort response, partially initialized 112 * @param json the reply to parse 113 * @return #GNUNET_OK on success 114 */ 115 static enum GNUNET_GenericReturnValue 116 check_abort_refund (struct TALER_MERCHANT_OrderAbortHandle *oah, 117 struct TALER_MERCHANT_AbortResponse *ar, 118 const json_t *json) 119 { 120 const json_t *refunds; 121 unsigned int num_refunds; 122 struct GNUNET_JSON_Specification spec[] = { 123 GNUNET_JSON_spec_array_const ("refunds", 124 &refunds), 125 GNUNET_JSON_spec_end () 126 }; 127 128 if (GNUNET_OK != 129 GNUNET_JSON_parse (json, 130 spec, 131 NULL, NULL)) 132 { 133 GNUNET_break_op (0); 134 return GNUNET_SYSERR; 135 } 136 num_refunds = (unsigned int) json_array_size (refunds); 137 if ( (json_array_size (refunds) != (size_t) num_refunds) || 138 (num_refunds > MAX_REFUNDS) ) 139 { 140 GNUNET_break (0); 141 return GNUNET_SYSERR; 142 } 143 144 { 145 struct TALER_MERCHANT_AbortedCoin res[GNUNET_NZL (num_refunds)]; 146 147 for (unsigned int i = 0; i<num_refunds; i++) 148 { 149 json_t *refund = json_array_get (refunds, i); 150 uint32_t exchange_status; 151 struct GNUNET_JSON_Specification spec_es[] = { 152 GNUNET_JSON_spec_uint32 ("exchange_status", 153 &exchange_status), 154 GNUNET_JSON_spec_end () 155 }; 156 157 if (GNUNET_OK != 158 GNUNET_JSON_parse (refund, 159 spec_es, 160 NULL, NULL)) 161 { 162 GNUNET_break_op (0); 163 return GNUNET_SYSERR; 164 } 165 if (MHD_HTTP_OK == exchange_status) 166 { 167 struct GNUNET_JSON_Specification spec_detail[] = { 168 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 169 &res[i].exchange_sig), 170 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 171 &res[i].exchange_pub), 172 GNUNET_JSON_spec_end () 173 }; 174 175 if (GNUNET_OK != 176 GNUNET_JSON_parse (refund, 177 spec_detail, 178 NULL, NULL)) 179 { 180 GNUNET_break_op (0); 181 return GNUNET_SYSERR; 182 } 183 184 if (GNUNET_OK != 185 TALER_exchange_online_refund_confirmation_verify ( 186 &oah->h_contract_terms, 187 &oah->coins[i].coin_pub, 188 &oah->merchant_pub, 189 0, /* transaction id */ 190 &oah->coins[i].amount_with_fee, 191 &res[i].exchange_pub, 192 &res[i].exchange_sig)) 193 { 194 GNUNET_break_op (0); 195 return GNUNET_SYSERR; 196 } 197 } 198 } 199 ar->details.ok.merchant_pub = &oah->merchant_pub; 200 ar->details.ok.num_aborts = num_refunds; 201 ar->details.ok.aborts = res; 202 oah->abort_cb (oah->abort_cb_cls, 203 ar); 204 oah->abort_cb = NULL; 205 } 206 return GNUNET_OK; 207 } 208 209 210 /** 211 * Function called when we're done processing the 212 * abort request. 213 * 214 * @param cls the `struct TALER_MERCHANT_OrderAbortHandle` 215 * @param response_code HTTP response code, 0 on error 216 * @param response response body, NULL if not in JSON 217 */ 218 static void 219 handle_abort_finished (void *cls, 220 long response_code, 221 const void *response) 222 { 223 struct TALER_MERCHANT_OrderAbortHandle *oah = cls; 224 const json_t *json = response; 225 struct TALER_MERCHANT_AbortResponse ar = { 226 .hr.http_status = (unsigned int) response_code, 227 .hr.reply = json 228 }; 229 230 oah->job = NULL; 231 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 232 "/pay completed with response code %u\n", 233 (unsigned int) response_code); 234 switch (response_code) 235 { 236 case 0: 237 ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 238 break; 239 case MHD_HTTP_OK: 240 if (GNUNET_OK == 241 check_abort_refund (oah, 242 &ar, 243 json)) 244 { 245 TALER_MERCHANT_order_abort_cancel (oah); 246 return; 247 } 248 ar.hr.http_status = 0; 249 ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 250 break; 251 case MHD_HTTP_BAD_REQUEST: 252 ar.hr.ec = TALER_JSON_get_error_code (json); 253 ar.hr.hint = TALER_JSON_get_error_hint (json); 254 /* This should never happen, either us or the 255 merchant is buggy (or API version conflict); just 256 pass JSON reply to the application */ 257 break; 258 case MHD_HTTP_FORBIDDEN: 259 ar.hr.ec = TALER_JSON_get_error_code (json); 260 ar.hr.hint = TALER_JSON_get_error_hint (json); 261 break; 262 case MHD_HTTP_NOT_FOUND: 263 ar.hr.ec = TALER_JSON_get_error_code (json); 264 ar.hr.hint = TALER_JSON_get_error_hint (json); 265 /* Nothing really to verify, this should never 266 happen, we should pass the JSON reply to the 267 application */ 268 break; 269 case MHD_HTTP_REQUEST_TIMEOUT: 270 ar.hr.ec = TALER_JSON_get_error_code (json); 271 ar.hr.hint = TALER_JSON_get_error_hint (json); 272 /* Nothing really to verify, merchant says one of 273 the signatures is invalid; as we checked them, 274 this should never happen, we should pass the JSON 275 reply to the application */ 276 break; 277 case MHD_HTTP_PRECONDITION_FAILED: 278 /* Our *payment* already succeeded fully. */ 279 ar.hr.ec = TALER_JSON_get_error_code (json); 280 ar.hr.hint = TALER_JSON_get_error_hint (json); 281 break; 282 case MHD_HTTP_INTERNAL_SERVER_ERROR: 283 ar.hr.ec = TALER_JSON_get_error_code (json); 284 ar.hr.hint = TALER_JSON_get_error_hint (json); 285 /* Server had an internal issue; we should retry, 286 but this API leaves this to the application */ 287 break; 288 case MHD_HTTP_BAD_GATEWAY: 289 TALER_MERCHANT_parse_error_details_ (json, 290 response_code, 291 &ar.hr); 292 /* Nothing really to verify, the merchant is blaming the exchange. 293 We should pass the JSON reply to the application */ 294 break; 295 default: 296 /* unexpected response code */ 297 TALER_MERCHANT_parse_error_details_ (json, 298 response_code, 299 &ar.hr); 300 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 301 "Unexpected response code %u/%d\n", 302 (unsigned int) response_code, 303 (int) ar.hr.ec); 304 GNUNET_break_op (0); 305 break; 306 } 307 oah->abort_cb (oah->abort_cb_cls, 308 &ar); 309 TALER_MERCHANT_order_abort_cancel (oah); 310 } 311 312 313 struct TALER_MERCHANT_OrderAbortHandle * 314 TALER_MERCHANT_order_abort ( 315 struct GNUNET_CURL_Context *ctx, 316 const char *merchant_url, 317 const char *order_id, 318 const struct TALER_MerchantPublicKeyP *merchant_pub, 319 const struct TALER_PrivateContractHashP *h_contract, 320 unsigned int num_coins, 321 const struct TALER_MERCHANT_AbortCoin coins[static num_coins], 322 TALER_MERCHANT_AbortCallback cb, 323 void *cb_cls) 324 { 325 struct TALER_MERCHANT_OrderAbortHandle *oah; 326 json_t *abort_obj; 327 json_t *j_coins; 328 329 j_coins = json_array (); 330 if (NULL == j_coins) 331 { 332 GNUNET_break (0); 333 return NULL; 334 } 335 for (unsigned int i = 0; i<num_coins; i++) 336 { 337 const struct TALER_MERCHANT_AbortCoin *ac = &coins[i]; 338 json_t *j_coin; 339 340 /* create JSON for this coin */ 341 j_coin = GNUNET_JSON_PACK ( 342 GNUNET_JSON_pack_data_auto ("coin_pub", 343 &ac->coin_pub), 344 /* FIXME: no longer needed since **v18**, remove eventually! */ 345 TALER_JSON_pack_amount ("contribution", 346 &ac->amount_with_fee), 347 GNUNET_JSON_pack_string ("exchange_url", 348 ac->exchange_url)); 349 if (0 != 350 json_array_append_new (j_coins, 351 j_coin)) 352 { 353 GNUNET_break (0); 354 json_decref (j_coins); 355 return NULL; 356 } 357 } 358 abort_obj = GNUNET_JSON_PACK ( 359 GNUNET_JSON_pack_array_steal ("coins", 360 j_coins), 361 GNUNET_JSON_pack_data_auto ("h_contract", 362 h_contract)); 363 oah = GNUNET_new (struct TALER_MERCHANT_OrderAbortHandle); 364 oah->h_contract_terms = *h_contract; 365 oah->merchant_pub = *merchant_pub; 366 oah->ctx = ctx; 367 oah->abort_cb = cb; 368 oah->abort_cb_cls = cb_cls; 369 { 370 char *path; 371 372 GNUNET_asprintf (&path, 373 "orders/%s/abort", 374 order_id); 375 oah->url = TALER_url_join (merchant_url, 376 path, 377 NULL); 378 GNUNET_free (path); 379 } 380 if (NULL == oah->url) 381 { 382 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 383 "Could not construct request URL.\n"); 384 json_decref (abort_obj); 385 GNUNET_free (oah); 386 return NULL; 387 } 388 oah->num_coins = num_coins; 389 oah->coins = GNUNET_new_array (num_coins, 390 struct TALER_MERCHANT_AbortCoin); 391 GNUNET_memcpy (oah->coins, 392 coins, 393 num_coins * sizeof (struct TALER_MERCHANT_AbortCoin)); 394 { 395 CURL *eh; 396 397 eh = TALER_MERCHANT_curl_easy_get_ (oah->url); 398 if (GNUNET_OK != 399 TALER_curl_easy_post (&oah->post_ctx, 400 eh, 401 abort_obj)) 402 { 403 GNUNET_break (0); 404 curl_easy_cleanup (eh); 405 json_decref (abort_obj); 406 GNUNET_free (oah); 407 return NULL; 408 } 409 json_decref (abort_obj); 410 oah->job = GNUNET_CURL_job_add2 (ctx, 411 eh, 412 oah->post_ctx.headers, 413 &handle_abort_finished, 414 oah); 415 } 416 return oah; 417 } 418 419 420 void 421 TALER_MERCHANT_order_abort_cancel ( 422 struct TALER_MERCHANT_OrderAbortHandle *oah) 423 { 424 if (NULL != oah->job) 425 { 426 GNUNET_CURL_job_cancel (oah->job); 427 oah->job = NULL; 428 } 429 TALER_curl_easy_post_finished (&oah->post_ctx); 430 GNUNET_free (oah->coins); 431 GNUNET_free (oah->url); 432 GNUNET_free (oah); 433 } 434 435 436 /* end of merchant_api_post_order_abort.c */