merchant_api_post-orders-ORDER_ID-abort.c (13036B)
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 merchant_api_post-orders-ORDER_ID-abort-new.c 21 * @brief Implementation of the POST /orders/$ID/abort request 22 * @author Christian Grothoff 23 */ 24 #include "taler/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/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 > MAX_REFUNDS) ) 145 { 146 GNUNET_break (0); 147 return GNUNET_SYSERR; 148 } 149 150 { 151 struct TALER_MERCHANT_PostOrdersAbortedCoin res[GNUNET_NZL (num_refunds)]; 152 153 for (unsigned int i = 0; i<num_refunds; i++) 154 { 155 json_t *refund = json_array_get (refunds, i); 156 uint32_t exchange_status; 157 struct GNUNET_JSON_Specification spec_es[] = { 158 GNUNET_JSON_spec_uint32 ("exchange_status", 159 &exchange_status), 160 GNUNET_JSON_spec_end () 161 }; 162 163 if (GNUNET_OK != 164 GNUNET_JSON_parse (refund, 165 spec_es, 166 NULL, NULL)) 167 { 168 GNUNET_break_op (0); 169 return GNUNET_SYSERR; 170 } 171 if (MHD_HTTP_OK == exchange_status) 172 { 173 struct GNUNET_JSON_Specification spec_detail[] = { 174 GNUNET_JSON_spec_fixed_auto ("exchange_sig", 175 &res[i].exchange_sig), 176 GNUNET_JSON_spec_fixed_auto ("exchange_pub", 177 &res[i].exchange_pub), 178 GNUNET_JSON_spec_end () 179 }; 180 181 if (GNUNET_OK != 182 GNUNET_JSON_parse (refund, 183 spec_detail, 184 NULL, NULL)) 185 { 186 GNUNET_break_op (0); 187 return GNUNET_SYSERR; 188 } 189 res[i].coin_pub = poah->coins[i].coin_pub; 190 191 if (GNUNET_OK != 192 TALER_exchange_online_refund_confirmation_verify ( 193 &poah->h_contract, 194 &poah->coins[i].coin_pub, 195 &poah->merchant_pub, 196 0, /* transaction id */ 197 &poah->coins[i].amount_with_fee, 198 &res[i].exchange_pub, 199 &res[i].exchange_sig)) 200 { 201 GNUNET_break_op (0); 202 return GNUNET_SYSERR; 203 } 204 } 205 } 206 switch (ar->hr.http_status) 207 { 208 case MHD_HTTP_OK: 209 ar->details.ok.num_aborts = num_refunds; 210 ar->details.ok.aborts = res; 211 break; 212 case MHD_HTTP_BAD_GATEWAY: 213 ar->details.bad_gateway.num_aborts = num_refunds; 214 ar->details.bad_gateway.aborts = res; 215 break; 216 default: 217 GNUNET_assert (0); 218 } 219 poah->cb (poah->cb_cls, 220 ar); 221 poah->cb = NULL; 222 } 223 return GNUNET_OK; 224 } 225 226 227 /** 228 * Function called when we're done processing the 229 * HTTP POST /orders/$ID/abort request. 230 * 231 * @param cls the `struct TALER_MERCHANT_PostOrdersAbortHandle` 232 * @param response_code HTTP response code, 0 on error 233 * @param response response body, NULL if not in JSON 234 */ 235 static void 236 handle_abort_finished (void *cls, 237 long response_code, 238 const void *response) 239 { 240 struct TALER_MERCHANT_PostOrdersAbortHandle *poah = cls; 241 const json_t *json = response; 242 struct TALER_MERCHANT_PostOrdersAbortResponse ar = { 243 .hr.http_status = (unsigned int) response_code, 244 .hr.reply = json 245 }; 246 247 poah->job = NULL; 248 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 249 "POST /orders/$ID/abort completed with response code %u\n", 250 (unsigned int) response_code); 251 switch (response_code) 252 { 253 case 0: 254 ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 255 break; 256 case MHD_HTTP_OK: 257 if (GNUNET_OK == 258 check_abort_refund (poah, 259 &ar, 260 json)) 261 { 262 TALER_MERCHANT_post_orders_abort_cancel (poah); 263 return; 264 } 265 ar.hr.http_status = 0; 266 ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 267 break; 268 case MHD_HTTP_BAD_REQUEST: 269 ar.hr.ec = TALER_JSON_get_error_code (json); 270 ar.hr.hint = TALER_JSON_get_error_hint (json); 271 break; 272 case MHD_HTTP_FORBIDDEN: 273 ar.hr.ec = TALER_JSON_get_error_code (json); 274 ar.hr.hint = TALER_JSON_get_error_hint (json); 275 break; 276 case MHD_HTTP_NOT_FOUND: 277 ar.hr.ec = TALER_JSON_get_error_code (json); 278 ar.hr.hint = TALER_JSON_get_error_hint (json); 279 break; 280 case MHD_HTTP_REQUEST_TIMEOUT: 281 ar.hr.ec = TALER_JSON_get_error_code (json); 282 ar.hr.hint = TALER_JSON_get_error_hint (json); 283 break; 284 case MHD_HTTP_PRECONDITION_FAILED: 285 ar.hr.ec = TALER_JSON_get_error_code (json); 286 ar.hr.hint = TALER_JSON_get_error_hint (json); 287 break; 288 case MHD_HTTP_INTERNAL_SERVER_ERROR: 289 ar.hr.ec = TALER_JSON_get_error_code (json); 290 ar.hr.hint = TALER_JSON_get_error_hint (json); 291 break; 292 case MHD_HTTP_BAD_GATEWAY: 293 if (GNUNET_OK == 294 check_abort_refund (poah, 295 &ar, 296 json)) 297 { 298 TALER_MERCHANT_post_orders_abort_cancel (poah); 299 return; 300 } 301 ar.hr.http_status = 0; 302 ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 303 break; 304 case MHD_HTTP_GATEWAY_TIMEOUT: 305 TALER_MERCHANT_parse_error_details_ (json, 306 response_code, 307 &ar.hr); 308 ar.details.gateway_timeout.exchange_ec = ar.hr.exchange_code; 309 ar.details.gateway_timeout.exchange_http_status 310 = ar.hr.exchange_http_status; 311 break; 312 default: 313 TALER_MERCHANT_parse_error_details_ (json, 314 response_code, 315 &ar.hr); 316 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 317 "Unexpected response code %u/%d\n", 318 (unsigned int) response_code, 319 (int) ar.hr.ec); 320 GNUNET_break_op (0); 321 break; 322 } 323 poah->cb (poah->cb_cls, 324 &ar); 325 TALER_MERCHANT_post_orders_abort_cancel (poah); 326 } 327 328 329 struct TALER_MERCHANT_PostOrdersAbortHandle * 330 TALER_MERCHANT_post_orders_abort_create ( 331 struct GNUNET_CURL_Context *ctx, 332 const char *url, 333 const char *order_id, 334 const struct TALER_MerchantPublicKeyP *merchant_pub, 335 const struct TALER_PrivateContractHashP *h_contract, 336 unsigned int num_coins, 337 const struct TALER_MERCHANT_PostOrdersAbortCoin coins[static num_coins]) 338 { 339 struct TALER_MERCHANT_PostOrdersAbortHandle *poah; 340 341 poah = GNUNET_new (struct TALER_MERCHANT_PostOrdersAbortHandle); 342 poah->ctx = ctx; 343 poah->base_url = GNUNET_strdup (url); 344 poah->order_id = GNUNET_strdup (order_id); 345 poah->merchant_pub = *merchant_pub; 346 poah->h_contract = *h_contract; 347 poah->num_coins = num_coins; 348 poah->coins = GNUNET_new_array (num_coins, 349 struct TALER_MERCHANT_PostOrdersAbortCoin); 350 GNUNET_memcpy ( 351 poah->coins, 352 coins, 353 num_coins * sizeof (struct TALER_MERCHANT_PostOrdersAbortCoin)); 354 return poah; 355 } 356 357 358 enum TALER_ErrorCode 359 TALER_MERCHANT_post_orders_abort_start ( 360 struct TALER_MERCHANT_PostOrdersAbortHandle *poah, 361 TALER_MERCHANT_PostOrdersAbortCallback cb, 362 TALER_MERCHANT_POST_ORDERS_ABORT_RESULT_CLOSURE *cb_cls) 363 { 364 json_t *abort_obj; 365 json_t *j_coins; 366 CURL *eh; 367 368 poah->cb = cb; 369 poah->cb_cls = cb_cls; 370 { 371 char *path; 372 373 GNUNET_asprintf (&path, 374 "orders/%s/abort", 375 poah->order_id); 376 poah->url = TALER_url_join (poah->base_url, 377 path, 378 NULL); 379 GNUNET_free (path); 380 } 381 if (NULL == poah->url) 382 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 383 j_coins = json_array (); 384 if (NULL == j_coins) 385 { 386 GNUNET_break (0); 387 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 388 } 389 for (unsigned int i = 0; i<poah->num_coins; i++) 390 { 391 const struct TALER_MERCHANT_PostOrdersAbortCoin *ac = &poah->coins[i]; 392 json_t *j_coin; 393 394 j_coin = GNUNET_JSON_PACK ( 395 GNUNET_JSON_pack_data_auto ("coin_pub", 396 &ac->coin_pub), 397 TALER_JSON_pack_amount ("contribution", 398 &ac->amount_with_fee), 399 GNUNET_JSON_pack_string ("exchange_url", 400 ac->exchange_url)); 401 if (0 != 402 json_array_append_new (j_coins, 403 j_coin)) 404 { 405 GNUNET_break (0); 406 json_decref (j_coins); 407 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 408 } 409 } 410 abort_obj = GNUNET_JSON_PACK ( 411 GNUNET_JSON_pack_array_steal ("coins", 412 j_coins), 413 GNUNET_JSON_pack_data_auto ("h_contract", 414 &poah->h_contract)); 415 eh = TALER_MERCHANT_curl_easy_get_ (poah->url); 416 if ( (NULL == eh) || 417 (GNUNET_OK != 418 TALER_curl_easy_post (&poah->post_ctx, 419 eh, 420 abort_obj)) ) 421 { 422 GNUNET_break (0); 423 json_decref (abort_obj); 424 if (NULL != eh) 425 curl_easy_cleanup (eh); 426 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 427 } 428 json_decref (abort_obj); 429 poah->job = GNUNET_CURL_job_add2 (poah->ctx, 430 eh, 431 poah->post_ctx.headers, 432 &handle_abort_finished, 433 poah); 434 if (NULL == poah->job) 435 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 436 return TALER_EC_NONE; 437 } 438 439 440 void 441 TALER_MERCHANT_post_orders_abort_cancel ( 442 struct TALER_MERCHANT_PostOrdersAbortHandle *poah) 443 { 444 if (NULL != poah->job) 445 { 446 GNUNET_CURL_job_cancel (poah->job); 447 poah->job = NULL; 448 } 449 TALER_curl_easy_post_finished (&poah->post_ctx); 450 GNUNET_free (poah->coins); 451 GNUNET_free (poah->order_id); 452 GNUNET_free (poah->url); 453 GNUNET_free (poah->base_url); 454 GNUNET_free (poah); 455 } 456 457 458 /* end of merchant_api_post-orders-ORDER_ID-abort-new.c */