merchant_api_get-private-orders-ORDER_ID.c (20318B)
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-private-orders-ORDER_ID-new.c 19 * @brief Implementation of the GET /private/orders/$ORDER_ID request 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-private-orders-ORDER_ID.h> 29 #include "merchant_api_curl_defaults.h" 30 #include <taler/taler_json_lib.h> 31 32 33 /** 34 * Maximum number of refund details we return. 35 */ 36 #define MAX_REFUND_DETAILS 1024 37 38 /** 39 * Maximum number of wire details we return. 40 */ 41 #define MAX_WIRE_DETAILS 1024 42 43 44 /** 45 * Handle for a GET /private/orders/$ORDER_ID operation. 46 */ 47 struct TALER_MERCHANT_GetPrivateOrderHandle 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_GetPrivateOrderCallback cb; 68 69 /** 70 * Closure for @a cb. 71 */ 72 TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE *cb_cls; 73 74 /** 75 * Reference to the execution context. 76 */ 77 struct GNUNET_CURL_Context *ctx; 78 79 /** 80 * Order ID. 81 */ 82 char *order_id; 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 * Long-poll ETag to suppress unchanged responses. 96 */ 97 struct GNUNET_ShortHashCode lp_not_etag; 98 99 /** 100 * True if @e lp_not_etag was set. 101 */ 102 bool have_lp_not_etag; 103 104 /** 105 * If true, try to obtain wire transfer status from the exchange. 106 */ 107 bool transfer; 108 109 /** 110 * If true, allow refunded orders under already_paid_order_id. 111 */ 112 bool allow_refunded_for_repurchase; 113 }; 114 115 116 /** 117 * Handle HTTP OK response for an unpaid order. 118 * 119 * @param oph handle for the request 120 * @param[in,out] osr HTTP response we got 121 */ 122 static void 123 handle_unpaid (struct TALER_MERCHANT_GetPrivateOrderHandle *oph, 124 struct TALER_MERCHANT_GetPrivateOrderResponse *osr) 125 { 126 struct GNUNET_JSON_Specification spec[] = { 127 GNUNET_JSON_spec_mark_optional ( 128 GNUNET_JSON_spec_string ( 129 "already_paid_order_id", 130 &osr->details.ok.details.unpaid.already_paid_order_id), 131 NULL), 132 GNUNET_JSON_spec_mark_optional ( 133 GNUNET_JSON_spec_string ( 134 "already_paid_fulfillment_url", 135 &osr->details.ok.details.unpaid.already_paid_fulfillment_url), 136 NULL), 137 GNUNET_JSON_spec_string ( 138 "taler_pay_uri", 139 &osr->details.ok.details.unpaid.taler_pay_uri), 140 GNUNET_JSON_spec_string ( 141 "summary", 142 &osr->details.ok.details.unpaid.summary), 143 GNUNET_JSON_spec_mark_optional ( 144 GNUNET_JSON_spec_object_const ( 145 "proto_contract_terms", 146 &osr->details.ok.details.unpaid.proto_contract_terms), 147 NULL), 148 GNUNET_JSON_spec_string ( 149 "order_status_url", 150 &osr->details.ok.details.unpaid.order_status_url), 151 GNUNET_JSON_spec_timestamp ( 152 "creation_time", 153 &osr->details.ok.details.unpaid.creation_time), 154 GNUNET_JSON_spec_mark_optional ( 155 GNUNET_JSON_spec_timestamp ( 156 "pay_deadline", 157 &osr->details.ok.details.unpaid.pay_deadline), 158 NULL), 159 GNUNET_JSON_spec_end () 160 }; 161 162 if (GNUNET_OK != 163 GNUNET_JSON_parse (osr->hr.reply, 164 spec, 165 NULL, NULL)) 166 { 167 GNUNET_break_op (0); 168 osr->hr.http_status = 0; 169 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 170 oph->cb (oph->cb_cls, 171 osr); 172 return; 173 } 174 osr->details.ok.status = TALER_MERCHANT_OSC_UNPAID; 175 oph->cb (oph->cb_cls, 176 osr); 177 } 178 179 180 /** 181 * Handle HTTP OK response for a claimed order. 182 * 183 * @param oph handle for the request 184 * @param[in,out] osr HTTP response we got 185 */ 186 static void 187 handle_claimed (struct TALER_MERCHANT_GetPrivateOrderHandle *oph, 188 struct TALER_MERCHANT_GetPrivateOrderResponse *osr) 189 { 190 struct GNUNET_JSON_Specification spec[] = { 191 GNUNET_JSON_spec_object_const ( 192 "contract_terms", 193 &osr->details.ok.details.claimed.contract_terms), 194 GNUNET_JSON_spec_mark_optional ( 195 GNUNET_JSON_spec_string ( 196 "order_status_url", 197 &osr->details.ok.details.claimed.order_status_url), 198 NULL), 199 GNUNET_JSON_spec_end () 200 }; 201 202 if (GNUNET_OK != 203 GNUNET_JSON_parse (osr->hr.reply, 204 spec, 205 NULL, NULL)) 206 { 207 GNUNET_break_op (0); 208 osr->hr.http_status = 0; 209 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 210 oph->cb (oph->cb_cls, 211 osr); 212 return; 213 } 214 osr->details.ok.status = TALER_MERCHANT_OSC_CLAIMED; 215 oph->cb (oph->cb_cls, 216 osr); 217 } 218 219 220 /** 221 * Handle HTTP OK response for a paid order. 222 * 223 * @param oph handle for the request 224 * @param[in,out] osr HTTP response we got 225 */ 226 static void 227 handle_paid (struct TALER_MERCHANT_GetPrivateOrderHandle *oph, 228 struct TALER_MERCHANT_GetPrivateOrderResponse *osr) 229 { 230 uint32_t hc32; 231 uint32_t ci32; 232 bool ci_missing; 233 const json_t *wire_details; 234 const json_t *refund_details; 235 struct GNUNET_JSON_Specification spec[] = { 236 GNUNET_JSON_spec_bool ("refunded", 237 &osr->details.ok.details.paid.refunded), 238 GNUNET_JSON_spec_bool ("refund_pending", 239 &osr->details.ok.details.paid.refund_pending), 240 GNUNET_JSON_spec_bool ("wired", 241 &osr->details.ok.details.paid.wired), 242 TALER_JSON_spec_amount_any ("deposit_total", 243 &osr->details.ok.details.paid.deposit_total), 244 TALER_JSON_spec_ec ("exchange_code", 245 &osr->details.ok.details.paid.exchange_ec), 246 GNUNET_JSON_spec_uint32 ("exchange_http_status", 247 &hc32), 248 TALER_JSON_spec_amount_any ("refund_amount", 249 &osr->details.ok.details.paid.refund_amount), 250 GNUNET_JSON_spec_object_const ( 251 "contract_terms", 252 &osr->details.ok.details.paid.contract_terms), 253 GNUNET_JSON_spec_mark_optional ( 254 GNUNET_JSON_spec_uint32 ("choice_index", 255 &ci32), 256 &ci_missing), 257 GNUNET_JSON_spec_array_const ("wire_details", 258 &wire_details), 259 GNUNET_JSON_spec_array_const ("refund_details", 260 &refund_details), 261 GNUNET_JSON_spec_mark_optional ( 262 GNUNET_JSON_spec_timestamp ("last_payment", 263 &osr->details.ok.details.paid.last_payment), 264 NULL), 265 GNUNET_JSON_spec_string ( 266 "order_status_url", 267 &osr->details.ok.details.paid.order_status_url), 268 GNUNET_JSON_spec_end () 269 }; 270 271 if (GNUNET_OK != 272 GNUNET_JSON_parse (osr->hr.reply, 273 spec, 274 NULL, NULL)) 275 { 276 GNUNET_break_op (0); 277 osr->hr.http_status = 0; 278 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 279 oph->cb (oph->cb_cls, 280 osr); 281 return; 282 } 283 osr->details.ok.status = TALER_MERCHANT_OSC_PAID; 284 osr->details.ok.details.paid.exchange_hc = (unsigned int) hc32; 285 osr->details.ok.details.paid.choice_index = ci_missing 286 ? -1 287 : (int) ci32; 288 { 289 unsigned int wts_len = (unsigned int) json_array_size (wire_details); 290 unsigned int ref_len = (unsigned int) json_array_size (refund_details); 291 292 if ( (json_array_size (wire_details) != (size_t) wts_len) || 293 (wts_len > MAX_WIRE_DETAILS) ) 294 { 295 GNUNET_break (0); 296 osr->hr.http_status = 0; 297 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 298 oph->cb (oph->cb_cls, 299 osr); 300 return; 301 } 302 if ( (json_array_size (refund_details) != (size_t) ref_len) || 303 (ref_len > MAX_REFUND_DETAILS) ) 304 { 305 GNUNET_break (0); 306 osr->hr.http_status = 0; 307 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 308 oph->cb (oph->cb_cls, 309 osr); 310 return; 311 } 312 { 313 struct TALER_MERCHANT_WireTransfer wts[GNUNET_NZL (wts_len)]; 314 struct TALER_MERCHANT_GetPrivateOrderRefundDetail ref[ 315 GNUNET_NZL (ref_len)]; 316 317 for (unsigned int i = 0; i<wts_len; i++) 318 { 319 struct TALER_MERCHANT_WireTransfer *wt = &wts[i]; 320 const json_t *w = json_array_get (wire_details, 321 i); 322 struct GNUNET_JSON_Specification ispec[] = { 323 TALER_JSON_spec_web_url ("exchange_url", 324 &wt->exchange_url), 325 GNUNET_JSON_spec_fixed_auto ("wtid", 326 &wt->wtid), 327 GNUNET_JSON_spec_timestamp ("execution_time", 328 &wt->execution_time), 329 TALER_JSON_spec_amount_any ("amount", 330 &wt->total_amount), 331 GNUNET_JSON_spec_mark_optional ( 332 TALER_JSON_spec_amount_any ("deposit_fee", 333 &wt->deposit_fee), 334 NULL), 335 GNUNET_JSON_spec_bool ("confirmed", 336 &wt->confirmed), 337 GNUNET_JSON_spec_mark_optional ( 338 GNUNET_JSON_spec_uint64 ("expected_transfer_serial_id", 339 &wt->expected_transfer_serial_id), 340 NULL), 341 GNUNET_JSON_spec_end () 342 }; 343 344 if (GNUNET_OK != 345 GNUNET_JSON_parse (w, 346 ispec, 347 NULL, NULL)) 348 { 349 GNUNET_break_op (0); 350 osr->hr.http_status = 0; 351 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 352 oph->cb (oph->cb_cls, 353 osr); 354 return; 355 } 356 } 357 358 for (unsigned int i = 0; i<ref_len; i++) 359 { 360 struct TALER_MERCHANT_GetPrivateOrderRefundDetail *ro = &ref[i]; 361 const json_t *w = json_array_get (refund_details, 362 i); 363 struct GNUNET_JSON_Specification ispec[] = { 364 TALER_JSON_spec_amount_any ("amount", 365 &ro->refund_amount), 366 GNUNET_JSON_spec_string ("reason", 367 &ro->reason), 368 GNUNET_JSON_spec_bool ("pending", 369 &ro->pending), 370 GNUNET_JSON_spec_timestamp ("timestamp", 371 &ro->refund_time), 372 GNUNET_JSON_spec_end () 373 }; 374 375 if (GNUNET_OK != 376 GNUNET_JSON_parse (w, 377 ispec, 378 NULL, NULL)) 379 { 380 GNUNET_break_op (0); 381 osr->hr.http_status = 0; 382 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 383 oph->cb (oph->cb_cls, 384 osr); 385 return; 386 } 387 } 388 389 osr->details.ok.details.paid.wts = wts; 390 osr->details.ok.details.paid.wts_len = wts_len; 391 osr->details.ok.details.paid.refunds = ref; 392 osr->details.ok.details.paid.refunds_len = ref_len; 393 oph->cb (oph->cb_cls, 394 osr); 395 } 396 } 397 } 398 399 400 /** 401 * Function called when we're done processing the 402 * HTTP GET /private/orders/$ORDER_ID request. 403 * 404 * @param cls the `struct TALER_MERCHANT_GetPrivateOrderHandle` 405 * @param response_code HTTP response code, 0 on error 406 * @param response response body, NULL if not in JSON 407 */ 408 static void 409 handle_get_private_order_finished (void *cls, 410 long response_code, 411 const void *response) 412 { 413 struct TALER_MERCHANT_GetPrivateOrderHandle *oph = cls; 414 const json_t *json = response; 415 const char *order_status; 416 struct TALER_MERCHANT_GetPrivateOrderResponse osr = { 417 .hr.http_status = (unsigned int) response_code, 418 .hr.reply = json 419 }; 420 421 oph->job = NULL; 422 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 423 "Got /private/orders/$ORDER_ID response with status code %u\n", 424 (unsigned int) response_code); 425 switch (response_code) 426 { 427 case MHD_HTTP_OK: 428 /* see below */ 429 break; 430 case MHD_HTTP_NOT_MODIFIED: 431 oph->cb (oph->cb_cls, 432 &osr); 433 TALER_MERCHANT_get_private_order_cancel (oph); 434 return; 435 case MHD_HTTP_ACCEPTED: 436 oph->cb (oph->cb_cls, 437 &osr); 438 TALER_MERCHANT_get_private_order_cancel (oph); 439 return; 440 case MHD_HTTP_UNAUTHORIZED: 441 osr.hr.ec = TALER_JSON_get_error_code (json); 442 osr.hr.hint = TALER_JSON_get_error_hint (json); 443 oph->cb (oph->cb_cls, 444 &osr); 445 TALER_MERCHANT_get_private_order_cancel (oph); 446 return; 447 case MHD_HTTP_NOT_FOUND: 448 osr.hr.ec = TALER_JSON_get_error_code (json); 449 osr.hr.hint = TALER_JSON_get_error_hint (json); 450 oph->cb (oph->cb_cls, 451 &osr); 452 TALER_MERCHANT_get_private_order_cancel (oph); 453 return; 454 case MHD_HTTP_GATEWAY_TIMEOUT: 455 osr.hr.ec = TALER_JSON_get_error_code (json); 456 osr.hr.hint = TALER_JSON_get_error_hint (json); 457 oph->cb (oph->cb_cls, 458 &osr); 459 TALER_MERCHANT_get_private_order_cancel (oph); 460 return; 461 default: 462 osr.hr.ec = TALER_JSON_get_error_code (json); 463 osr.hr.hint = TALER_JSON_get_error_hint (json); 464 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 465 "Polling order status failed with HTTP status code %u/%d\n", 466 (unsigned int) response_code, 467 (int) osr.hr.ec); 468 GNUNET_break_op (0); 469 oph->cb (oph->cb_cls, 470 &osr); 471 TALER_MERCHANT_get_private_order_cancel (oph); 472 return; 473 } 474 475 order_status = json_string_value (json_object_get (json, 476 "order_status")); 477 if (NULL == order_status) 478 { 479 GNUNET_break_op (0); 480 osr.hr.http_status = 0; 481 osr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 482 oph->cb (oph->cb_cls, 483 &osr); 484 TALER_MERCHANT_get_private_order_cancel (oph); 485 return; 486 } 487 488 if (0 == strcmp ("paid", 489 order_status)) 490 { 491 handle_paid (oph, 492 &osr); 493 } 494 else if (0 == strcmp ("claimed", 495 order_status)) 496 { 497 handle_claimed (oph, 498 &osr); 499 } 500 else if (0 == strcmp ("unpaid", 501 order_status)) 502 { 503 handle_unpaid (oph, 504 &osr); 505 } 506 else 507 { 508 GNUNET_break_op (0); 509 osr.hr.http_status = 0; 510 osr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 511 oph->cb (oph->cb_cls, 512 &osr); 513 } 514 TALER_MERCHANT_get_private_order_cancel (oph); 515 } 516 517 518 struct TALER_MERCHANT_GetPrivateOrderHandle * 519 TALER_MERCHANT_get_private_order_create ( 520 struct GNUNET_CURL_Context *ctx, 521 const char *url, 522 const char *order_id) 523 { 524 struct TALER_MERCHANT_GetPrivateOrderHandle *oph; 525 526 oph = GNUNET_new (struct TALER_MERCHANT_GetPrivateOrderHandle); 527 oph->ctx = ctx; 528 oph->base_url = GNUNET_strdup (url); 529 oph->order_id = GNUNET_strdup (order_id); 530 return oph; 531 } 532 533 534 enum GNUNET_GenericReturnValue 535 TALER_MERCHANT_get_private_order_set_options_ ( 536 struct TALER_MERCHANT_GetPrivateOrderHandle *oph, 537 unsigned int num_options, 538 const struct TALER_MERCHANT_GetPrivateOrderOptionValue *options) 539 { 540 for (unsigned int i = 0; i < num_options; i++) 541 { 542 const struct TALER_MERCHANT_GetPrivateOrderOptionValue *opt = 543 &options[i]; 544 545 switch (opt->option) 546 { 547 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_END: 548 return GNUNET_OK; 549 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_SESSION_ID: 550 GNUNET_free (oph->session_id); 551 if (NULL != opt->details.session_id) 552 oph->session_id = GNUNET_strdup (opt->details.session_id); 553 break; 554 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TIMEOUT: 555 oph->timeout = opt->details.timeout; 556 break; 557 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TRANSFER: 558 oph->transfer = opt->details.transfer; 559 break; 560 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_LP_NOT_ETAG: 561 if (NULL != opt->details.lp_not_etag) 562 { 563 oph->lp_not_etag = *opt->details.lp_not_etag; 564 oph->have_lp_not_etag = true; 565 } 566 break; 567 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE: 568 oph->allow_refunded_for_repurchase 569 = opt->details.allow_refunded_for_repurchase; 570 break; 571 default: 572 GNUNET_break (0); 573 return GNUNET_NO; 574 } 575 } 576 return GNUNET_OK; 577 } 578 579 580 enum TALER_ErrorCode 581 TALER_MERCHANT_get_private_order_start ( 582 struct TALER_MERCHANT_GetPrivateOrderHandle *oph, 583 TALER_MERCHANT_GetPrivateOrderCallback cb, 584 TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE *cb_cls) 585 { 586 CURL *eh; 587 unsigned int tms; 588 589 oph->cb = cb; 590 oph->cb_cls = cb_cls; 591 tms = (unsigned int) (oph->timeout.rel_value_us 592 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 593 { 594 char *path; 595 char timeout_ms[32]; 596 char etag_str[sizeof (struct GNUNET_ShortHashCode) * 2 + 1]; 597 598 GNUNET_snprintf (timeout_ms, 599 sizeof (timeout_ms), 600 "%u", 601 tms); 602 if (oph->have_lp_not_etag) 603 { 604 char *end; 605 606 end = GNUNET_STRINGS_data_to_string ( 607 &oph->lp_not_etag, 608 sizeof (oph->lp_not_etag), 609 etag_str, 610 sizeof (etag_str) - 1); 611 *end = '\0'; 612 } 613 GNUNET_asprintf (&path, 614 "private/orders/%s", 615 oph->order_id); 616 oph->url = TALER_url_join (oph->base_url, 617 path, 618 "session_id", 619 oph->session_id, 620 "timeout_ms", 621 (0 != tms) 622 ? timeout_ms 623 : NULL, 624 "transfer", 625 oph->transfer 626 ? "YES" 627 : NULL, 628 "lp_not_etag", 629 oph->have_lp_not_etag 630 ? etag_str 631 : NULL, 632 "allow_refunded_for_repurchase", 633 oph->allow_refunded_for_repurchase 634 ? "YES" 635 : NULL, 636 NULL); 637 GNUNET_free (path); 638 } 639 if (NULL == oph->url) 640 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 641 eh = TALER_MERCHANT_curl_easy_get_ (oph->url); 642 if (NULL == eh) 643 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 644 if (0 != tms) 645 { 646 GNUNET_break (CURLE_OK == 647 curl_easy_setopt (eh, 648 CURLOPT_TIMEOUT_MS, 649 (long) (tms + 100L))); 650 } 651 oph->job = GNUNET_CURL_job_add (oph->ctx, 652 eh, 653 &handle_get_private_order_finished, 654 oph); 655 if (NULL == oph->job) 656 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 657 return TALER_EC_NONE; 658 } 659 660 661 void 662 TALER_MERCHANT_get_private_order_cancel ( 663 struct TALER_MERCHANT_GetPrivateOrderHandle *oph) 664 { 665 if (NULL != oph->job) 666 { 667 GNUNET_CURL_job_cancel (oph->job); 668 oph->job = NULL; 669 } 670 GNUNET_free (oph->url); 671 GNUNET_free (oph->order_id); 672 GNUNET_free (oph->session_id); 673 GNUNET_free (oph->base_url); 674 GNUNET_free (oph); 675 } 676 677 678 /* end of merchant_api_get-private-orders-ORDER_ID-new.c */