merchant_api_get-private-orders-ORDER_ID.c (20544B)
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 src/lib/merchant_api_get-private-orders-ORDER_ID.c 19 * @brief Implementation of the GET /private/orders/$ORDER_ID request 20 * @author Christian Grothoff 21 */ 22 #include "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/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 TALER_JSON_spec_slug ( 129 "already_paid_order_id", 130 &osr->details.ok.details.unpaid.already_paid_order_id), 131 NULL), 132 GNUNET_JSON_spec_mark_optional ( 133 TALER_JSON_spec_web_url ( 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 TALER_JSON_spec_web_url ( 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 TALER_JSON_spec_web_url ( 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_array_const ( 263 "refunds_external", 264 &osr->details.ok.details.paid.refunds_external), 265 NULL), 266 GNUNET_JSON_spec_mark_optional ( 267 GNUNET_JSON_spec_timestamp ("last_payment", 268 &osr->details.ok.details.paid.last_payment), 269 NULL), 270 TALER_JSON_spec_web_url ( 271 "order_status_url", 272 &osr->details.ok.details.paid.order_status_url), 273 GNUNET_JSON_spec_end () 274 }; 275 276 if (GNUNET_OK != 277 GNUNET_JSON_parse (osr->hr.reply, 278 spec, 279 NULL, NULL)) 280 { 281 GNUNET_break_op (0); 282 osr->hr.http_status = 0; 283 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 284 oph->cb (oph->cb_cls, 285 osr); 286 return; 287 } 288 osr->details.ok.status = TALER_MERCHANT_OSC_PAID; 289 osr->details.ok.details.paid.exchange_hc = (unsigned int) hc32; 290 osr->details.ok.details.paid.choice_index = ci_missing 291 ? -1 292 : (int) ci32; 293 { 294 unsigned int wts_len = (unsigned int) json_array_size (wire_details); 295 unsigned int ref_len = (unsigned int) json_array_size (refund_details); 296 297 if ( (json_array_size (wire_details) != (size_t) wts_len) || 298 (wts_len > MAX_WIRE_DETAILS) ) 299 { 300 GNUNET_break (0); 301 osr->hr.http_status = 0; 302 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 303 oph->cb (oph->cb_cls, 304 osr); 305 return; 306 } 307 if ( (json_array_size (refund_details) != (size_t) ref_len) || 308 (ref_len > MAX_REFUND_DETAILS) ) 309 { 310 GNUNET_break (0); 311 osr->hr.http_status = 0; 312 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 313 oph->cb (oph->cb_cls, 314 osr); 315 return; 316 } 317 { 318 struct TALER_MERCHANT_WireTransfer wts[GNUNET_NZL (wts_len)]; 319 struct TALER_MERCHANT_GetPrivateOrderRefundDetail ref[ 320 GNUNET_NZL (ref_len)]; 321 322 memset (wts, 323 0, 324 sizeof (wts)); 325 for (unsigned int i = 0; i<wts_len; i++) 326 { 327 struct TALER_MERCHANT_WireTransfer *wt = &wts[i]; 328 const json_t *w = json_array_get (wire_details, 329 i); 330 struct GNUNET_JSON_Specification ispec[] = { 331 TALER_JSON_spec_web_url ("exchange_url", 332 &wt->exchange_url), 333 GNUNET_JSON_spec_fixed_auto ("wtid", 334 &wt->wtid), 335 GNUNET_JSON_spec_timestamp ("execution_time", 336 &wt->execution_time), 337 TALER_JSON_spec_amount_any ("amount", 338 &wt->total_amount), 339 GNUNET_JSON_spec_mark_optional ( 340 TALER_JSON_spec_amount_any ("deposit_fee", 341 &wt->deposit_fee), 342 NULL), 343 GNUNET_JSON_spec_bool ("confirmed", 344 &wt->confirmed), 345 GNUNET_JSON_spec_mark_optional ( 346 GNUNET_JSON_spec_uint64 ("expected_transfer_serial_id", 347 &wt->expected_transfer_serial_id), 348 NULL), 349 GNUNET_JSON_spec_end () 350 }; 351 352 if (GNUNET_OK != 353 GNUNET_JSON_parse (w, 354 ispec, 355 NULL, NULL)) 356 { 357 GNUNET_break_op (0); 358 osr->hr.http_status = 0; 359 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 360 oph->cb (oph->cb_cls, 361 osr); 362 return; 363 } 364 } 365 366 for (unsigned int i = 0; i<ref_len; i++) 367 { 368 struct TALER_MERCHANT_GetPrivateOrderRefundDetail *ro = &ref[i]; 369 const json_t *w = json_array_get (refund_details, 370 i); 371 struct GNUNET_JSON_Specification ispec[] = { 372 TALER_JSON_spec_amount_any ("amount", 373 &ro->refund_amount), 374 GNUNET_JSON_spec_string ("reason", 375 &ro->reason), 376 GNUNET_JSON_spec_bool ("pending", 377 &ro->pending), 378 GNUNET_JSON_spec_timestamp ("timestamp", 379 &ro->refund_time), 380 GNUNET_JSON_spec_end () 381 }; 382 383 if (GNUNET_OK != 384 GNUNET_JSON_parse (w, 385 ispec, 386 NULL, NULL)) 387 { 388 GNUNET_break_op (0); 389 osr->hr.http_status = 0; 390 osr->hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 391 oph->cb (oph->cb_cls, 392 osr); 393 return; 394 } 395 } 396 397 osr->details.ok.details.paid.wts = wts; 398 osr->details.ok.details.paid.wts_len = wts_len; 399 osr->details.ok.details.paid.refunds = ref; 400 osr->details.ok.details.paid.refunds_len = ref_len; 401 oph->cb (oph->cb_cls, 402 osr); 403 } 404 } 405 } 406 407 408 /** 409 * Function called when we're done processing the 410 * HTTP GET /private/orders/$ORDER_ID request. 411 * 412 * @param cls the `struct TALER_MERCHANT_GetPrivateOrderHandle` 413 * @param response_code HTTP response code, 0 on error 414 * @param response response body, NULL if not in JSON 415 */ 416 static void 417 handle_get_private_order_finished (void *cls, 418 long response_code, 419 const void *response) 420 { 421 struct TALER_MERCHANT_GetPrivateOrderHandle *oph = cls; 422 const json_t *json = response; 423 const char *order_status; 424 struct TALER_MERCHANT_GetPrivateOrderResponse osr = { 425 .hr.http_status = (unsigned int) response_code, 426 .hr.reply = json 427 }; 428 429 oph->job = NULL; 430 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 431 "Got /private/orders/$ORDER_ID response with status code %u\n", 432 (unsigned int) response_code); 433 switch (response_code) 434 { 435 case MHD_HTTP_OK: 436 /* see below */ 437 break; 438 case MHD_HTTP_NOT_MODIFIED: 439 oph->cb (oph->cb_cls, 440 &osr); 441 TALER_MERCHANT_get_private_order_cancel (oph); 442 return; 443 case MHD_HTTP_ACCEPTED: 444 oph->cb (oph->cb_cls, 445 &osr); 446 TALER_MERCHANT_get_private_order_cancel (oph); 447 return; 448 case MHD_HTTP_UNAUTHORIZED: 449 osr.hr.ec = TALER_JSON_get_error_code (json); 450 osr.hr.hint = TALER_JSON_get_error_hint (json); 451 oph->cb (oph->cb_cls, 452 &osr); 453 TALER_MERCHANT_get_private_order_cancel (oph); 454 return; 455 case MHD_HTTP_NOT_FOUND: 456 osr.hr.ec = TALER_JSON_get_error_code (json); 457 osr.hr.hint = TALER_JSON_get_error_hint (json); 458 oph->cb (oph->cb_cls, 459 &osr); 460 TALER_MERCHANT_get_private_order_cancel (oph); 461 return; 462 case MHD_HTTP_GATEWAY_TIMEOUT: 463 osr.hr.ec = TALER_JSON_get_error_code (json); 464 osr.hr.hint = TALER_JSON_get_error_hint (json); 465 oph->cb (oph->cb_cls, 466 &osr); 467 TALER_MERCHANT_get_private_order_cancel (oph); 468 return; 469 default: 470 osr.hr.ec = TALER_JSON_get_error_code (json); 471 osr.hr.hint = TALER_JSON_get_error_hint (json); 472 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 473 "Polling order status failed with HTTP status code %u/%d\n", 474 (unsigned int) response_code, 475 (int) osr.hr.ec); 476 GNUNET_break_op (0); 477 oph->cb (oph->cb_cls, 478 &osr); 479 TALER_MERCHANT_get_private_order_cancel (oph); 480 return; 481 } 482 483 order_status = json_string_value (json_object_get (json, 484 "order_status")); 485 if (NULL == order_status) 486 { 487 GNUNET_break_op (0); 488 osr.hr.http_status = 0; 489 osr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 490 oph->cb (oph->cb_cls, 491 &osr); 492 TALER_MERCHANT_get_private_order_cancel (oph); 493 return; 494 } 495 496 if (0 == strcmp ("paid", 497 order_status)) 498 { 499 handle_paid (oph, 500 &osr); 501 } 502 else if (0 == strcmp ("claimed", 503 order_status)) 504 { 505 handle_claimed (oph, 506 &osr); 507 } 508 else if (0 == strcmp ("unpaid", 509 order_status)) 510 { 511 handle_unpaid (oph, 512 &osr); 513 } 514 else 515 { 516 GNUNET_break_op (0); 517 osr.hr.http_status = 0; 518 osr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 519 oph->cb (oph->cb_cls, 520 &osr); 521 } 522 TALER_MERCHANT_get_private_order_cancel (oph); 523 } 524 525 526 struct TALER_MERCHANT_GetPrivateOrderHandle * 527 TALER_MERCHANT_get_private_order_create ( 528 struct GNUNET_CURL_Context *ctx, 529 const char *url, 530 const char *order_id) 531 { 532 struct TALER_MERCHANT_GetPrivateOrderHandle *oph; 533 534 oph = GNUNET_new (struct TALER_MERCHANT_GetPrivateOrderHandle); 535 oph->ctx = ctx; 536 oph->base_url = GNUNET_strdup (url); 537 oph->order_id = GNUNET_strdup (order_id); 538 return oph; 539 } 540 541 542 enum GNUNET_GenericReturnValue 543 TALER_MERCHANT_get_private_order_set_options_ ( 544 struct TALER_MERCHANT_GetPrivateOrderHandle *oph, 545 unsigned int num_options, 546 const struct TALER_MERCHANT_GetPrivateOrderOptionValue *options) 547 { 548 for (unsigned int i = 0; i < num_options; i++) 549 { 550 const struct TALER_MERCHANT_GetPrivateOrderOptionValue *opt = 551 &options[i]; 552 553 switch (opt->option) 554 { 555 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_END: 556 return GNUNET_OK; 557 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_SESSION_ID: 558 GNUNET_free (oph->session_id); 559 if (NULL != opt->details.session_id) 560 oph->session_id = GNUNET_strdup (opt->details.session_id); 561 break; 562 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TIMEOUT: 563 oph->timeout = opt->details.timeout; 564 break; 565 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_TRANSFER: 566 oph->transfer = opt->details.transfer; 567 break; 568 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_LP_NOT_ETAG: 569 if (NULL != opt->details.lp_not_etag) 570 { 571 oph->lp_not_etag = *opt->details.lp_not_etag; 572 oph->have_lp_not_etag = true; 573 } 574 break; 575 case TALER_MERCHANT_GET_PRIVATE_ORDER_OPTION_ALLOW_REFUNDED_FOR_REPURCHASE: 576 oph->allow_refunded_for_repurchase 577 = opt->details.allow_refunded_for_repurchase; 578 break; 579 default: 580 GNUNET_break (0); 581 return GNUNET_NO; 582 } 583 } 584 return GNUNET_OK; 585 } 586 587 588 enum TALER_ErrorCode 589 TALER_MERCHANT_get_private_order_start ( 590 struct TALER_MERCHANT_GetPrivateOrderHandle *oph, 591 TALER_MERCHANT_GetPrivateOrderCallback cb, 592 TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE *cb_cls) 593 { 594 CURL *eh; 595 unsigned int tms; 596 597 oph->cb = cb; 598 oph->cb_cls = cb_cls; 599 tms = (unsigned int) (oph->timeout.rel_value_us 600 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 601 { 602 char *path; 603 char timeout_ms[32]; 604 char etag_str[sizeof (struct GNUNET_ShortHashCode) * 2 + 1]; 605 606 GNUNET_snprintf (timeout_ms, 607 sizeof (timeout_ms), 608 "%u", 609 tms); 610 if (oph->have_lp_not_etag) 611 { 612 char *end; 613 614 end = GNUNET_STRINGS_data_to_string ( 615 &oph->lp_not_etag, 616 sizeof (oph->lp_not_etag), 617 etag_str, 618 sizeof (etag_str) - 1); 619 *end = '\0'; 620 } 621 GNUNET_asprintf (&path, 622 "private/orders/%s", 623 oph->order_id); 624 oph->url = TALER_url_join (oph->base_url, 625 path, 626 "session_id", 627 oph->session_id, 628 "timeout_ms", 629 (0 != tms) 630 ? timeout_ms 631 : NULL, 632 "transfer", 633 oph->transfer 634 ? "YES" 635 : NULL, 636 "lp_not_etag", 637 oph->have_lp_not_etag 638 ? etag_str 639 : NULL, 640 "allow_refunded_for_repurchase", 641 oph->allow_refunded_for_repurchase 642 ? "YES" 643 : NULL, 644 NULL); 645 GNUNET_free (path); 646 } 647 if (NULL == oph->url) 648 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 649 eh = TALER_MERCHANT_curl_easy_get_ (oph->url); 650 if (NULL == eh) 651 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 652 if (0 != tms) 653 { 654 GNUNET_break (CURLE_OK == 655 curl_easy_setopt (eh, 656 CURLOPT_TIMEOUT_MS, 657 (long) (tms + 100L))); 658 } 659 oph->job = GNUNET_CURL_job_add (oph->ctx, 660 eh, 661 &handle_get_private_order_finished, 662 oph); 663 if (NULL == oph->job) 664 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 665 return TALER_EC_NONE; 666 } 667 668 669 void 670 TALER_MERCHANT_get_private_order_cancel ( 671 struct TALER_MERCHANT_GetPrivateOrderHandle *oph) 672 { 673 if (NULL != oph->job) 674 { 675 GNUNET_CURL_job_cancel (oph->job); 676 oph->job = NULL; 677 } 678 GNUNET_free (oph->url); 679 GNUNET_free (oph->order_id); 680 GNUNET_free (oph->session_id); 681 GNUNET_free (oph->base_url); 682 GNUNET_free (oph); 683 } 684 685 686 /* end of merchant_api_get-private-orders-ORDER_ID-new.c */