merchant_api_post-orders-ORDER_ID-abort.c (14816B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2026 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 src/lib/merchant_api_post-orders-ORDER_ID-abort.c 21 * @brief Implementation of the POST /orders/$ID/abort request 22 * @author Christian Grothoff 23 */ 24 #include "platform.h" 25 #include <curl/curl.h> 26 #include <jansson.h> 27 #include <microhttpd.h> /* just for HTTP status codes */ 28 #include <gnunet/gnunet_util_lib.h> 29 #include <gnunet/gnunet_curl_lib.h> 30 #include <taler/merchant/post-orders-ORDER_ID-abort.h> 31 #include "merchant_api_curl_defaults.h" 32 #include "merchant_api_common.h" 33 #include <taler/taler_json_lib.h> 34 #include <taler/taler_curl_lib.h> 35 #include <taler/taler_signatures.h> 36 37 38 /** 39 * Maximum number of refunds we return. 40 */ 41 #define MAX_REFUNDS 1024 42 43 44 /** 45 * Handle for a POST /orders/$ORDER_ID/abort operation. 46 */ 47 struct TALER_MERCHANT_PostOrdersAbortHandle 48 { 49 /** 50 * Base URL of the merchant backend. 51 */ 52 char *base_url; 53 54 /** 55 * The full URL for this request. 56 */ 57 char *url; 58 59 /** 60 * Handle for the request. 61 */ 62 struct GNUNET_CURL_Job *job; 63 64 /** 65 * Function to call with the result. 66 */ 67 TALER_MERCHANT_PostOrdersAbortCallback cb; 68 69 /** 70 * Closure for @a cb. 71 */ 72 TALER_MERCHANT_POST_ORDERS_ABORT_RESULT_CLOSURE *cb_cls; 73 74 /** 75 * Reference to the execution context. 76 */ 77 struct GNUNET_CURL_Context *ctx; 78 79 /** 80 * Minor context that holds body and headers. 81 */ 82 struct TALER_CURL_PostContext post_ctx; 83 84 /** 85 * Order identifier. 86 */ 87 char *order_id; 88 89 /** 90 * Public key of the merchant. 91 */ 92 struct TALER_MerchantPublicKeyP merchant_pub; 93 94 /** 95 * Hash of the contract terms. 96 */ 97 struct TALER_PrivateContractHashP h_contract; 98 99 /** 100 * The coins we are aborting on. 101 */ 102 struct TALER_MERCHANT_PostOrdersAbortCoin *coins; 103 104 /** 105 * Number of @e coins. 106 */ 107 unsigned int num_coins; 108 }; 109 110 111 /** 112 * Check that the response for an abort is well-formed, 113 * and call the application callback with the result if it is 114 * OK. Otherwise returns #GNUNET_SYSERR. 115 * 116 * @param poah handle to operation that created the reply 117 * @param[in] ar abort response, partially initialized 118 * @param json the reply to parse 119 * @return #GNUNET_OK on success 120 */ 121 static enum GNUNET_GenericReturnValue 122 check_abort_refund (struct TALER_MERCHANT_PostOrdersAbortHandle *poah, 123 struct TALER_MERCHANT_PostOrdersAbortResponse *ar, 124 const json_t *json) 125 { 126 const json_t *refunds; 127 unsigned int num_refunds; 128 struct GNUNET_JSON_Specification spec[] = { 129 GNUNET_JSON_spec_array_const ("refunds", 130 &refunds), 131 GNUNET_JSON_spec_end () 132 }; 133 134 if (GNUNET_OK != 135 GNUNET_JSON_parse (json, 136 spec, 137 NULL, NULL)) 138 { 139 GNUNET_break_op (0); 140 return GNUNET_SYSERR; 141 } 142 num_refunds = (unsigned int) json_array_size (refunds); 143 if ( (json_array_size (refunds) != (size_t) num_refunds) || 144 (num_refunds != poah->num_coins) ) 145 { 146 GNUNET_break (0); 147 return GNUNET_SYSERR; 148 } 149 150 { 151 struct TALER_MERCHANT_PostOrdersAbortedCoin res[GNUNET_NZL (num_refunds)]; 152 153 memset (res, 154 0, 155 sizeof (res)); 156 for (unsigned int i = 0; i<num_refunds; i++) 157 { 158 json_t *refund = json_array_get (refunds, i); 159 struct TALER_MERCHANT_PostOrdersAbortedCoin *ac = &res[i]; 160 const char *type; 161 struct GNUNET_JSON_Specification spec_type[] = { 162 GNUNET_JSON_spec_string ("type", 163 &type), 164 GNUNET_JSON_spec_end () 165 }; 166 167 ac->coin_pub = poah->coins[i].coin_pub; 168 if (GNUNET_OK != 169 GNUNET_JSON_parse (refund, 170 spec_type, 171 NULL, NULL)) 172 { 173 GNUNET_break_op (0); 174 return GNUNET_SYSERR; 175 } 176 if (0 == strcmp (type, 177 "undeposited")) 178 { 179 /* An "undeposited" entry (MerchantAbortPayRefundUndepositedStatus) 180 carries no exchange_status and no refund confirmation to verify. */ 181 ac->type = TALER_MERCHANT_POAC_UNDEPOSITED; 182 continue; 183 } 184 if (0 == strcmp (type, 185 "success")) 186 { 187 struct GNUNET_JSON_Specification spec_detail[] = { 188 GNUNET_JSON_spec_uint32 ("exchange_status", 189 &ac->details.success.exchange_status), 190 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 191 &ac->details.success.exchange_sig), 192 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 193 &ac->details.success.exchange_pub), 194 GNUNET_JSON_spec_end () 195 }; 196 197 ac->type = TALER_MERCHANT_POAC_SUCCESS; 198 if (GNUNET_OK != 199 GNUNET_JSON_parse (refund, 200 spec_detail, 201 NULL, NULL)) 202 { 203 GNUNET_break_op (0); 204 return GNUNET_SYSERR; 205 } 206 if (MHD_HTTP_OK != ac->details.success.exchange_status) 207 { 208 GNUNET_break_op (0); 209 return GNUNET_SYSERR; 210 } 211 212 if (GNUNET_OK != 213 TALER_exchange_online_refund_confirmation_verify ( 214 &poah->h_contract, 215 &poah->coins[i].coin_pub, 216 &poah->merchant_pub, 217 0, /* transaction id */ 218 &poah->coins[i].amount_with_fee, 219 &ac->details.success.exchange_pub, 220 &ac->details.success.exchange_sig)) 221 { 222 GNUNET_break_op (0); 223 return GNUNET_SYSERR; 224 } 225 continue; 226 } 227 if (0 == strcmp (type, 228 "failure")) 229 { 230 struct GNUNET_JSON_Specification spec_detail[] = { 231 GNUNET_JSON_spec_uint32 ("exchange_status", 232 &ac->details.failure.exchange_status), 233 TALER_JSON_spec_ec ("exchange_code", 234 &ac->details.failure.exchange_code), 235 GNUNET_JSON_spec_mark_optional ( 236 GNUNET_JSON_spec_object_const ("exchange_reply", 237 &ac->details.failure.exchange_reply), 238 NULL), 239 GNUNET_JSON_spec_end () 240 }; 241 242 ac->type = TALER_MERCHANT_POAC_FAILURE; 243 if (GNUNET_OK != 244 GNUNET_JSON_parse (refund, 245 spec_detail, 246 NULL, NULL)) 247 { 248 GNUNET_break_op (0); 249 return GNUNET_SYSERR; 250 } 251 continue; 252 } 253 GNUNET_break_op (0); 254 return GNUNET_SYSERR; 255 } 256 switch (ar->hr.http_status) 257 { 258 case MHD_HTTP_OK: 259 ar->details.ok.num_aborts = num_refunds; 260 ar->details.ok.aborts = res; 261 break; 262 case MHD_HTTP_BAD_GATEWAY: 263 ar->details.bad_gateway.num_aborts = num_refunds; 264 ar->details.bad_gateway.aborts = res; 265 break; 266 default: 267 GNUNET_assert (0); 268 } 269 poah->cb (poah->cb_cls, 270 ar); 271 poah->cb = NULL; 272 } 273 return GNUNET_OK; 274 } 275 276 277 /** 278 * Function called when we're done processing the 279 * HTTP POST /orders/$ID/abort request. 280 * 281 * @param cls the `struct TALER_MERCHANT_PostOrdersAbortHandle` 282 * @param response_code HTTP response code, 0 on error 283 * @param response response body, NULL if not in JSON 284 */ 285 static void 286 handle_abort_finished (void *cls, 287 long response_code, 288 const void *response) 289 { 290 struct TALER_MERCHANT_PostOrdersAbortHandle *poah = cls; 291 const json_t *json = response; 292 struct TALER_MERCHANT_PostOrdersAbortResponse ar = { 293 .hr.http_status = (unsigned int) response_code, 294 .hr.reply = json 295 }; 296 297 poah->job = NULL; 298 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 299 "POST /orders/$ID/abort completed with response code %u\n", 300 (unsigned int) response_code); 301 switch (response_code) 302 { 303 case 0: 304 ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 305 break; 306 case MHD_HTTP_OK: 307 if (GNUNET_OK == 308 check_abort_refund (poah, 309 &ar, 310 json)) 311 { 312 TALER_MERCHANT_post_orders_abort_cancel (poah); 313 return; 314 } 315 ar.hr.http_status = 0; 316 ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 317 break; 318 case MHD_HTTP_BAD_REQUEST: 319 ar.hr.ec = TALER_JSON_get_error_code (json); 320 ar.hr.hint = TALER_JSON_get_error_hint (json); 321 break; 322 case MHD_HTTP_FORBIDDEN: 323 ar.hr.ec = TALER_JSON_get_error_code (json); 324 ar.hr.hint = TALER_JSON_get_error_hint (json); 325 break; 326 case MHD_HTTP_NOT_FOUND: 327 ar.hr.ec = TALER_JSON_get_error_code (json); 328 ar.hr.hint = TALER_JSON_get_error_hint (json); 329 break; 330 case MHD_HTTP_REQUEST_TIMEOUT: 331 ar.hr.ec = TALER_JSON_get_error_code (json); 332 ar.hr.hint = TALER_JSON_get_error_hint (json); 333 break; 334 case MHD_HTTP_PRECONDITION_FAILED: 335 ar.hr.ec = TALER_JSON_get_error_code (json); 336 ar.hr.hint = TALER_JSON_get_error_hint (json); 337 break; 338 case MHD_HTTP_INTERNAL_SERVER_ERROR: 339 ar.hr.ec = TALER_JSON_get_error_code (json); 340 ar.hr.hint = TALER_JSON_get_error_hint (json); 341 break; 342 case MHD_HTTP_BAD_GATEWAY: 343 if (GNUNET_OK == 344 check_abort_refund (poah, 345 &ar, 346 json)) 347 { 348 TALER_MERCHANT_post_orders_abort_cancel (poah); 349 return; 350 } 351 ar.hr.http_status = 0; 352 ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 353 break; 354 case MHD_HTTP_GATEWAY_TIMEOUT: 355 TALER_MERCHANT_parse_error_details_ (json, 356 response_code, 357 &ar.hr); 358 ar.details.gateway_timeout.exchange_ec = ar.hr.exchange_code; 359 ar.details.gateway_timeout.exchange_http_status 360 = ar.hr.exchange_http_status; 361 break; 362 default: 363 TALER_MERCHANT_parse_error_details_ (json, 364 response_code, 365 &ar.hr); 366 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 367 "Unexpected response code %u/%d\n", 368 (unsigned int) response_code, 369 (int) ar.hr.ec); 370 GNUNET_break_op (0); 371 break; 372 } 373 poah->cb (poah->cb_cls, 374 &ar); 375 TALER_MERCHANT_post_orders_abort_cancel (poah); 376 } 377 378 379 struct TALER_MERCHANT_PostOrdersAbortHandle * 380 TALER_MERCHANT_post_orders_abort_create ( 381 struct GNUNET_CURL_Context *ctx, 382 const char *url, 383 const char *order_id, 384 const struct TALER_MerchantPublicKeyP *merchant_pub, 385 const struct TALER_PrivateContractHashP *h_contract, 386 unsigned int num_coins, 387 const struct TALER_MERCHANT_PostOrdersAbortCoin coins[static num_coins]) 388 { 389 struct TALER_MERCHANT_PostOrdersAbortHandle *poah; 390 391 if (MAX_REFUNDS < num_coins) 392 { 393 GNUNET_break (0); 394 return NULL; 395 } 396 poah = GNUNET_new (struct TALER_MERCHANT_PostOrdersAbortHandle); 397 poah->ctx = ctx; 398 poah->base_url = GNUNET_strdup (url); 399 poah->order_id = GNUNET_strdup (order_id); 400 poah->merchant_pub = *merchant_pub; 401 poah->h_contract = *h_contract; 402 poah->num_coins = num_coins; 403 poah->coins = GNUNET_new_array (num_coins, 404 struct TALER_MERCHANT_PostOrdersAbortCoin); 405 GNUNET_memcpy ( 406 poah->coins, 407 coins, 408 num_coins * sizeof (struct TALER_MERCHANT_PostOrdersAbortCoin)); 409 return poah; 410 } 411 412 413 enum TALER_ErrorCode 414 TALER_MERCHANT_post_orders_abort_start ( 415 struct TALER_MERCHANT_PostOrdersAbortHandle *poah, 416 TALER_MERCHANT_PostOrdersAbortCallback cb, 417 TALER_MERCHANT_POST_ORDERS_ABORT_RESULT_CLOSURE *cb_cls) 418 { 419 json_t *abort_obj; 420 json_t *j_coins; 421 CURL *eh; 422 423 poah->cb = cb; 424 poah->cb_cls = cb_cls; 425 { 426 char *path; 427 428 GNUNET_asprintf (&path, 429 "orders/%s/abort", 430 poah->order_id); 431 poah->url = TALER_url_join (poah->base_url, 432 path, 433 NULL); 434 GNUNET_free (path); 435 } 436 if (NULL == poah->url) 437 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 438 j_coins = json_array (); 439 if (NULL == j_coins) 440 { 441 GNUNET_break (0); 442 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 443 } 444 for (unsigned int i = 0; i<poah->num_coins; i++) 445 { 446 const struct TALER_MERCHANT_PostOrdersAbortCoin *ac = &poah->coins[i]; 447 json_t *j_coin; 448 449 j_coin = GNUNET_JSON_PACK ( 450 GNUNET_JSON_pack_data_auto ("coin_pub", 451 &ac->coin_pub), 452 GNUNET_JSON_pack_string ("exchange_url", 453 ac->exchange_url)); 454 if (0 != 455 json_array_append_new (j_coins, 456 j_coin)) 457 { 458 GNUNET_break (0); 459 json_decref (j_coins); 460 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 461 } 462 } 463 abort_obj = GNUNET_JSON_PACK ( 464 GNUNET_JSON_pack_array_steal ("coins", 465 j_coins), 466 GNUNET_JSON_pack_data_auto ("h_contract", 467 &poah->h_contract)); 468 eh = TALER_MERCHANT_curl_easy_get_ (poah->url); 469 if ( (NULL == eh) || 470 (GNUNET_OK != 471 TALER_curl_easy_post (&poah->post_ctx, 472 eh, 473 abort_obj)) ) 474 { 475 GNUNET_break (0); 476 json_decref (abort_obj); 477 if (NULL != eh) 478 curl_easy_cleanup (eh); 479 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 480 } 481 json_decref (abort_obj); 482 poah->job = GNUNET_CURL_job_add2 (poah->ctx, 483 eh, 484 poah->post_ctx.headers, 485 &handle_abort_finished, 486 poah); 487 if (NULL == poah->job) 488 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 489 return TALER_EC_NONE; 490 } 491 492 493 void 494 TALER_MERCHANT_post_orders_abort_cancel ( 495 struct TALER_MERCHANT_PostOrdersAbortHandle *poah) 496 { 497 if (NULL != poah->job) 498 { 499 GNUNET_CURL_job_cancel (poah->job); 500 poah->job = NULL; 501 } 502 TALER_curl_easy_post_finished (&poah->post_ctx); 503 GNUNET_free (poah->coins); 504 GNUNET_free (poah->order_id); 505 GNUNET_free (poah->url); 506 GNUNET_free (poah->base_url); 507 GNUNET_free (poah); 508 } 509 510 511 /* end of merchant_api_post-orders-ORDER_ID-abort-new.c */