merchant_api_get-orders-ORDER_ID.c (13265B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2018-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser General Public License as published by the Free Software 7 Foundation; either version 2.1, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 12 13 You should have received a copy of the GNU Lesser General Public License along with 14 TALER; see the file COPYING.LGPL. If not, see 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file merchant_api_get-orders-ORDER_ID-new.c 19 * @brief Implementation of the GET /orders/$ORDER_ID request (wallet-facing) 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <curl/curl.h> 24 #include <jansson.h> 25 #include <microhttpd.h> /* just for HTTP status codes */ 26 #include <gnunet/gnunet_util_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include <taler/taler-merchant/get-orders-ORDER_ID.h> 29 #include "merchant_api_curl_defaults.h" 30 #include "merchant_api_common.h" 31 #include <taler/taler_json_lib.h> 32 33 34 /** 35 * Handle for a GET /orders/$ORDER_ID operation (wallet-facing). 36 */ 37 struct TALER_MERCHANT_GetOrdersHandle 38 { 39 /** 40 * Base URL of the merchant backend. 41 */ 42 char *base_url; 43 44 /** 45 * The full URL for this request. 46 */ 47 char *url; 48 49 /** 50 * Handle for the request. 51 */ 52 struct GNUNET_CURL_Job *job; 53 54 /** 55 * Function to call with the result. 56 */ 57 TALER_MERCHANT_GetOrdersCallback cb; 58 59 /** 60 * Closure for @a cb. 61 */ 62 TALER_MERCHANT_GET_ORDERS_RESULT_CLOSURE *cb_cls; 63 64 /** 65 * Reference to the execution context. 66 */ 67 struct GNUNET_CURL_Context *ctx; 68 69 /** 70 * Order ID. 71 */ 72 char *order_id; 73 74 /** 75 * Hash of the contract terms (for authentication). 76 */ 77 struct TALER_PrivateContractHashP h_contract; 78 79 /** 80 * Claim token for unclaimed order authentication. 81 */ 82 struct TALER_ClaimTokenP token; 83 84 /** 85 * Session ID for repurchase detection, or NULL. 86 */ 87 char *session_id; 88 89 /** 90 * Long polling timeout. 91 */ 92 struct GNUNET_TIME_Relative timeout; 93 94 /** 95 * Minimum refund amount to wait for, or NULL if unset. 96 */ 97 struct TALER_Amount min_refund; 98 99 /** 100 * Whether refunded orders count for repurchase detection. 101 */ 102 enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase; 103 104 /** 105 * True if @e h_contract was set. 106 */ 107 bool have_h_contract; 108 109 /** 110 * True if @e token was set. 111 */ 112 bool have_token; 113 114 /** 115 * True if @e min_refund was set. 116 */ 117 bool have_min_refund; 118 119 /** 120 * If true, wait until refund is confirmed obtained. 121 */ 122 bool await_refund_obtained; 123 }; 124 125 126 /** 127 * Function called when we're done processing the 128 * HTTP GET /orders/$ORDER_ID request (wallet-facing). 129 * 130 * @param cls the `struct TALER_MERCHANT_GetOrdersHandle` 131 * @param response_code HTTP response code, 0 on error 132 * @param response response body, NULL if not in JSON 133 */ 134 static void 135 handle_get_order_finished (void *cls, 136 long response_code, 137 const void *response) 138 { 139 struct TALER_MERCHANT_GetOrdersHandle *oph = cls; 140 const json_t *json = response; 141 struct TALER_MERCHANT_GetOrdersResponse owgr = { 142 .hr.http_status = (unsigned int) response_code, 143 .hr.reply = json 144 }; 145 146 oph->job = NULL; 147 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 148 "Got /orders/$ORDER_ID response with status code %u\n", 149 (unsigned int) response_code); 150 switch (response_code) 151 { 152 case MHD_HTTP_OK: 153 { 154 struct GNUNET_JSON_Specification spec[] = { 155 GNUNET_JSON_spec_bool ("refunded", 156 &owgr.details.ok.refunded), 157 GNUNET_JSON_spec_bool ("refund_pending", 158 &owgr.details.ok.refund_pending), 159 TALER_JSON_spec_amount_any ("refund_amount", 160 &owgr.details.ok.refund_amount), 161 GNUNET_JSON_spec_mark_optional ( 162 TALER_JSON_spec_amount_any ("refund_taken", 163 &owgr.details.ok.refund_taken), 164 NULL), 165 GNUNET_JSON_spec_end () 166 }; 167 168 if (GNUNET_OK != 169 GNUNET_JSON_parse (json, 170 spec, 171 NULL, NULL)) 172 { 173 owgr.hr.http_status = 0; 174 owgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 175 break; 176 } 177 oph->cb (oph->cb_cls, 178 &owgr); 179 TALER_MERCHANT_get_orders_cancel (oph); 180 return; 181 } 182 case MHD_HTTP_ACCEPTED: 183 { 184 struct GNUNET_JSON_Specification spec[] = { 185 GNUNET_JSON_spec_string ( 186 "public_reorder_url", 187 &owgr.details.accepted.public_reorder_url), 188 GNUNET_JSON_spec_end () 189 }; 190 191 if (GNUNET_OK != 192 GNUNET_JSON_parse (json, 193 spec, 194 NULL, NULL)) 195 { 196 owgr.hr.http_status = 0; 197 owgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 198 break; 199 } 200 oph->cb (oph->cb_cls, 201 &owgr); 202 TALER_MERCHANT_get_orders_cancel (oph); 203 return; 204 } 205 case MHD_HTTP_FOUND: 206 /* Redirect; Location header has the target URL. 207 No JSON body expected. */ 208 break; 209 case MHD_HTTP_PAYMENT_REQUIRED: 210 { 211 struct GNUNET_JSON_Specification spec[] = { 212 GNUNET_JSON_spec_string ( 213 "taler_pay_uri", 214 &owgr.details.payment_required.taler_pay_uri), 215 GNUNET_JSON_spec_mark_optional ( 216 GNUNET_JSON_spec_string ( 217 "already_paid_order_id", 218 &owgr.details.payment_required.already_paid_order_id), 219 NULL), 220 GNUNET_JSON_spec_mark_optional ( 221 GNUNET_JSON_spec_string ( 222 "fulfillment_url", 223 &owgr.details.payment_required.fulfillment_url), 224 NULL), 225 GNUNET_JSON_spec_end () 226 }; 227 228 if (GNUNET_OK != 229 GNUNET_JSON_parse (json, 230 spec, 231 NULL, NULL)) 232 { 233 owgr.hr.http_status = 0; 234 owgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 235 break; 236 } 237 oph->cb (oph->cb_cls, 238 &owgr); 239 TALER_MERCHANT_get_orders_cancel (oph); 240 return; 241 } 242 case MHD_HTTP_BAD_REQUEST: 243 owgr.hr.ec = TALER_JSON_get_error_code (json); 244 owgr.hr.hint = TALER_JSON_get_error_hint (json); 245 break; 246 case MHD_HTTP_FORBIDDEN: 247 owgr.hr.ec = TALER_JSON_get_error_code (json); 248 owgr.hr.hint = TALER_JSON_get_error_hint (json); 249 break; 250 case MHD_HTTP_NOT_FOUND: 251 owgr.hr.ec = TALER_JSON_get_error_code (json); 252 owgr.hr.hint = TALER_JSON_get_error_hint (json); 253 break; 254 case MHD_HTTP_NOT_ACCEPTABLE: 255 owgr.hr.ec = TALER_JSON_get_error_code (json); 256 owgr.hr.hint = TALER_JSON_get_error_hint (json); 257 break; 258 case MHD_HTTP_CONFLICT: 259 owgr.hr.ec = TALER_JSON_get_error_code (json); 260 owgr.hr.hint = TALER_JSON_get_error_hint (json); 261 break; 262 case MHD_HTTP_INTERNAL_SERVER_ERROR: 263 owgr.hr.ec = TALER_JSON_get_error_code (json); 264 owgr.hr.hint = TALER_JSON_get_error_hint (json); 265 break; 266 default: 267 TALER_MERCHANT_parse_error_details_ (json, 268 response_code, 269 &owgr.hr); 270 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 271 "Unexpected response code %u/%d\n", 272 (unsigned int) response_code, 273 (int) owgr.hr.ec); 274 break; 275 } 276 oph->cb (oph->cb_cls, 277 &owgr); 278 TALER_MERCHANT_get_orders_cancel (oph); 279 } 280 281 282 struct TALER_MERCHANT_GetOrdersHandle * 283 TALER_MERCHANT_get_orders_create ( 284 struct GNUNET_CURL_Context *ctx, 285 const char *url, 286 const char *order_id) 287 { 288 struct TALER_MERCHANT_GetOrdersHandle *oph; 289 290 oph = GNUNET_new (struct TALER_MERCHANT_GetOrdersHandle); 291 oph->ctx = ctx; 292 oph->base_url = GNUNET_strdup (url); 293 oph->order_id = GNUNET_strdup (order_id); 294 oph->allow_refunded_for_repurchase = TALER_EXCHANGE_YNA_NO; 295 return oph; 296 } 297 298 299 enum GNUNET_GenericReturnValue 300 TALER_MERCHANT_get_orders_set_options_ ( 301 struct TALER_MERCHANT_GetOrdersHandle *oph, 302 unsigned int num_options, 303 const struct TALER_MERCHANT_GetOrdersOptionValue *options) 304 { 305 for (unsigned int i = 0; i < num_options; i++) 306 { 307 const struct TALER_MERCHANT_GetOrdersOptionValue *opt = 308 &options[i]; 309 310 switch (opt->option) 311 { 312 case TALER_MERCHANT_GET_ORDERS_OPTION_END: 313 return GNUNET_OK; 314 case TALER_MERCHANT_GET_ORDERS_OPTION_TIMEOUT: 315 oph->timeout = opt->details.timeout; 316 break; 317 case TALER_MERCHANT_GET_ORDERS_OPTION_SESSION_ID: 318 GNUNET_free (oph->session_id); 319 if (NULL != opt->details.session_id) 320 oph->session_id = GNUNET_strdup (opt->details.session_id); 321 break; 322 case TALER_MERCHANT_GET_ORDERS_OPTION_MIN_REFUND: 323 oph->min_refund = opt->details.min_refund; 324 oph->have_min_refund = true; 325 break; 326 case TALER_MERCHANT_GET_ORDERS_OPTION_AWAIT_REFUND_OBTAINED: 327 oph->await_refund_obtained = opt->details.await_refund_obtained; 328 break; 329 case TALER_MERCHANT_GET_ORDERS_OPTION_H_CONTRACT: 330 oph->h_contract = opt->details.h_contract; 331 oph->have_h_contract = true; 332 break; 333 case TALER_MERCHANT_GET_ORDERS_OPTION_TOKEN: 334 oph->token = opt->details.token; 335 oph->have_token = true; 336 break; 337 case TALER_MERCHANT_GET_ORDERS_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE: 338 oph->allow_refunded_for_repurchase 339 = opt->details.allow_refunded_for_repurchase; 340 break; 341 default: 342 GNUNET_break (0); 343 return GNUNET_NO; 344 } 345 } 346 return GNUNET_OK; 347 } 348 349 350 enum TALER_ErrorCode 351 TALER_MERCHANT_get_orders_start ( 352 struct TALER_MERCHANT_GetOrdersHandle *oph, 353 TALER_MERCHANT_GetOrdersCallback cb, 354 TALER_MERCHANT_GET_ORDERS_RESULT_CLOSURE *cb_cls) 355 { 356 CURL *eh; 357 unsigned int tms; 358 359 oph->cb = cb; 360 oph->cb_cls = cb_cls; 361 tms = (unsigned int) (oph->timeout.rel_value_us 362 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 363 { 364 struct GNUNET_CRYPTO_HashAsciiEncoded h_contract_s; 365 char *token_s = NULL; 366 char *path; 367 char timeout_ms[32]; 368 const char *yna_s; 369 370 if (oph->have_h_contract) 371 GNUNET_CRYPTO_hash_to_enc (&oph->h_contract.hash, 372 &h_contract_s); 373 if (oph->have_token) 374 token_s = GNUNET_STRINGS_data_to_string_alloc ( 375 &oph->token, 376 sizeof (oph->token)); 377 GNUNET_snprintf (timeout_ms, 378 sizeof (timeout_ms), 379 "%u", 380 tms); 381 GNUNET_asprintf (&path, 382 "orders/%s", 383 oph->order_id); 384 yna_s = (TALER_EXCHANGE_YNA_NO != oph->allow_refunded_for_repurchase) 385 ? TALER_yna_to_string (oph->allow_refunded_for_repurchase) 386 : NULL; 387 oph->url = TALER_url_join (oph->base_url, 388 path, 389 "h_contract", 390 oph->have_h_contract 391 ? h_contract_s.encoding 392 : NULL, 393 "token", 394 token_s, 395 "session_id", 396 oph->session_id, 397 "timeout_ms", 398 (0 != tms) 399 ? timeout_ms 400 : NULL, 401 "refund", 402 oph->have_min_refund 403 ? TALER_amount2s (&oph->min_refund) 404 : NULL, 405 "await_refund_obtained", 406 oph->await_refund_obtained 407 ? "yes" 408 : NULL, 409 "allow_refunded_for_repurchase", 410 yna_s, 411 NULL); 412 GNUNET_free (path); 413 GNUNET_free (token_s); 414 } 415 if (NULL == oph->url) 416 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 417 eh = TALER_MERCHANT_curl_easy_get_ (oph->url); 418 if (NULL == eh) 419 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 420 if (0 != tms) 421 { 422 GNUNET_break (CURLE_OK == 423 curl_easy_setopt (eh, 424 CURLOPT_TIMEOUT_MS, 425 (long) (tms + 100L))); 426 } 427 oph->job = GNUNET_CURL_job_add (oph->ctx, 428 eh, 429 &handle_get_order_finished, 430 oph); 431 if (NULL == oph->job) 432 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 433 return TALER_EC_NONE; 434 } 435 436 437 void 438 TALER_MERCHANT_get_orders_cancel ( 439 struct TALER_MERCHANT_GetOrdersHandle *oph) 440 { 441 if (NULL != oph->job) 442 { 443 GNUNET_CURL_job_cancel (oph->job); 444 oph->job = NULL; 445 } 446 GNUNET_free (oph->url); 447 GNUNET_free (oph->order_id); 448 GNUNET_free (oph->session_id); 449 GNUNET_free (oph->base_url); 450 GNUNET_free (oph); 451 } 452 453 454 /* end of merchant_api_get-orders-ORDER_ID-new.c */