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