merchant_api_post-orders-ORDER_ID-abort.c (12461B)
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 ar->details.ok.num_aborts = num_refunds; 207 ar->details.ok.aborts = res; 208 poah->cb (poah->cb_cls, 209 ar); 210 poah->cb = NULL; 211 } 212 return GNUNET_OK; 213 } 214 215 216 /** 217 * Function called when we're done processing the 218 * HTTP POST /orders/$ID/abort request. 219 * 220 * @param cls the `struct TALER_MERCHANT_PostOrdersAbortHandle` 221 * @param response_code HTTP response code, 0 on error 222 * @param response response body, NULL if not in JSON 223 */ 224 static void 225 handle_abort_finished (void *cls, 226 long response_code, 227 const void *response) 228 { 229 struct TALER_MERCHANT_PostOrdersAbortHandle *poah = cls; 230 const json_t *json = response; 231 struct TALER_MERCHANT_PostOrdersAbortResponse ar = { 232 .hr.http_status = (unsigned int) response_code, 233 .hr.reply = json 234 }; 235 236 poah->job = NULL; 237 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 238 "POST /orders/$ID/abort completed with response code %u\n", 239 (unsigned int) response_code); 240 switch (response_code) 241 { 242 case 0: 243 ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 244 break; 245 case MHD_HTTP_OK: 246 if (GNUNET_OK == 247 check_abort_refund (poah, 248 &ar, 249 json)) 250 { 251 TALER_MERCHANT_post_orders_abort_cancel (poah); 252 return; 253 } 254 ar.hr.http_status = 0; 255 ar.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 256 break; 257 case MHD_HTTP_BAD_REQUEST: 258 ar.hr.ec = TALER_JSON_get_error_code (json); 259 ar.hr.hint = TALER_JSON_get_error_hint (json); 260 break; 261 case MHD_HTTP_FORBIDDEN: 262 ar.hr.ec = TALER_JSON_get_error_code (json); 263 ar.hr.hint = TALER_JSON_get_error_hint (json); 264 break; 265 case MHD_HTTP_NOT_FOUND: 266 ar.hr.ec = TALER_JSON_get_error_code (json); 267 ar.hr.hint = TALER_JSON_get_error_hint (json); 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 break; 273 case MHD_HTTP_PRECONDITION_FAILED: 274 ar.hr.ec = TALER_JSON_get_error_code (json); 275 ar.hr.hint = TALER_JSON_get_error_hint (json); 276 break; 277 case MHD_HTTP_INTERNAL_SERVER_ERROR: 278 ar.hr.ec = TALER_JSON_get_error_code (json); 279 ar.hr.hint = TALER_JSON_get_error_hint (json); 280 break; 281 case MHD_HTTP_BAD_GATEWAY: 282 TALER_MERCHANT_parse_error_details_ (json, 283 response_code, 284 &ar.hr); 285 break; 286 case MHD_HTTP_GATEWAY_TIMEOUT: 287 ar.hr.ec = TALER_JSON_get_error_code (json); 288 ar.hr.hint = TALER_JSON_get_error_hint (json); 289 break; 290 default: 291 TALER_MERCHANT_parse_error_details_ (json, 292 response_code, 293 &ar.hr); 294 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 295 "Unexpected response code %u/%d\n", 296 (unsigned int) response_code, 297 (int) ar.hr.ec); 298 GNUNET_break_op (0); 299 break; 300 } 301 poah->cb (poah->cb_cls, 302 &ar); 303 TALER_MERCHANT_post_orders_abort_cancel (poah); 304 } 305 306 307 struct TALER_MERCHANT_PostOrdersAbortHandle * 308 TALER_MERCHANT_post_orders_abort_create ( 309 struct GNUNET_CURL_Context *ctx, 310 const char *url, 311 const char *order_id, 312 const struct TALER_MerchantPublicKeyP *merchant_pub, 313 const struct TALER_PrivateContractHashP *h_contract, 314 unsigned int num_coins, 315 const struct TALER_MERCHANT_PostOrdersAbortCoin coins[static num_coins]) 316 { 317 struct TALER_MERCHANT_PostOrdersAbortHandle *poah; 318 319 poah = GNUNET_new (struct TALER_MERCHANT_PostOrdersAbortHandle); 320 poah->ctx = ctx; 321 poah->base_url = GNUNET_strdup (url); 322 poah->order_id = GNUNET_strdup (order_id); 323 poah->merchant_pub = *merchant_pub; 324 poah->h_contract = *h_contract; 325 poah->num_coins = num_coins; 326 poah->coins = GNUNET_new_array (num_coins, 327 struct TALER_MERCHANT_PostOrdersAbortCoin); 328 GNUNET_memcpy (poah->coins, 329 coins, 330 num_coins * sizeof (struct TALER_MERCHANT_PostOrdersAbortCoin)) 331 ; 332 return poah; 333 } 334 335 336 enum TALER_ErrorCode 337 TALER_MERCHANT_post_orders_abort_start ( 338 struct TALER_MERCHANT_PostOrdersAbortHandle *poah, 339 TALER_MERCHANT_PostOrdersAbortCallback cb, 340 TALER_MERCHANT_POST_ORDERS_ABORT_RESULT_CLOSURE *cb_cls) 341 { 342 json_t *abort_obj; 343 json_t *j_coins; 344 CURL *eh; 345 346 poah->cb = cb; 347 poah->cb_cls = cb_cls; 348 { 349 char *path; 350 351 GNUNET_asprintf (&path, 352 "orders/%s/abort", 353 poah->order_id); 354 poah->url = TALER_url_join (poah->base_url, 355 path, 356 NULL); 357 GNUNET_free (path); 358 } 359 if (NULL == poah->url) 360 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 361 j_coins = json_array (); 362 if (NULL == j_coins) 363 { 364 GNUNET_break (0); 365 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 366 } 367 for (unsigned int i = 0; i<poah->num_coins; i++) 368 { 369 const struct TALER_MERCHANT_PostOrdersAbortCoin *ac = &poah->coins[i]; 370 json_t *j_coin; 371 372 j_coin = GNUNET_JSON_PACK ( 373 GNUNET_JSON_pack_data_auto ("coin_pub", 374 &ac->coin_pub), 375 TALER_JSON_pack_amount ("contribution", 376 &ac->amount_with_fee), 377 GNUNET_JSON_pack_string ("exchange_url", 378 ac->exchange_url)); 379 if (0 != 380 json_array_append_new (j_coins, 381 j_coin)) 382 { 383 GNUNET_break (0); 384 json_decref (j_coins); 385 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 386 } 387 } 388 abort_obj = GNUNET_JSON_PACK ( 389 GNUNET_JSON_pack_array_steal ("coins", 390 j_coins), 391 GNUNET_JSON_pack_data_auto ("h_contract", 392 &poah->h_contract)); 393 eh = TALER_MERCHANT_curl_easy_get_ (poah->url); 394 if ( (NULL == eh) || 395 (GNUNET_OK != 396 TALER_curl_easy_post (&poah->post_ctx, 397 eh, 398 abort_obj)) ) 399 { 400 GNUNET_break (0); 401 json_decref (abort_obj); 402 if (NULL != eh) 403 curl_easy_cleanup (eh); 404 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 405 } 406 json_decref (abort_obj); 407 poah->job = GNUNET_CURL_job_add2 (poah->ctx, 408 eh, 409 poah->post_ctx.headers, 410 &handle_abort_finished, 411 poah); 412 if (NULL == poah->job) 413 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 414 return TALER_EC_NONE; 415 } 416 417 418 void 419 TALER_MERCHANT_post_orders_abort_cancel ( 420 struct TALER_MERCHANT_PostOrdersAbortHandle *poah) 421 { 422 if (NULL != poah->job) 423 { 424 GNUNET_CURL_job_cancel (poah->job); 425 poah->job = NULL; 426 } 427 TALER_curl_easy_post_finished (&poah->post_ctx); 428 GNUNET_free (poah->coins); 429 GNUNET_free (poah->order_id); 430 GNUNET_free (poah->url); 431 GNUNET_free (poah->base_url); 432 GNUNET_free (poah); 433 } 434 435 436 /* end of merchant_api_post-orders-ORDER_ID-abort-new.c */