taler-merchant-httpd_get-orders-ID.c (50222B)
1 /* 2 This file is part of TALER 3 (C) 2014-2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, 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 General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-merchant-httpd_get-orders-ID.c 18 * @brief implementation of GET /orders/$ID 19 * @author Marcello Stanisci 20 * @author Christian Grothoff 21 */ 22 #include "platform.h" 23 #include <jansson.h> 24 #include <gnunet/gnunet_uri_lib.h> 25 #include <gnunet/gnunet_common.h> 26 #include <taler/taler_signatures.h> 27 #include <taler/taler_dbevents.h> 28 #include <taler/taler_json_lib.h> 29 #include <taler/taler_templating_lib.h> 30 #include <taler/taler_exchange_service.h> 31 #include "taler-merchant-httpd_helper.h" 32 #include "taler-merchant-httpd_get-orders-ID.h" 33 #include "taler-merchant-httpd_mhd.h" 34 #include "taler-merchant-httpd_qr.h" 35 #include "taler/taler_error_codes.h" 36 #include "taler/taler_util.h" 37 #include "taler_merchant_util.h" 38 39 /** 40 * How often do we retry DB transactions on serialization failures? 41 */ 42 #define MAX_RETRIES 5 43 44 45 /** 46 * The different phases in which we handle the request. 47 */ 48 enum Phase 49 { 50 GOP_INIT = 0, 51 GOP_LOOKUP_TERMS = 1, 52 GOP_PARSE_CONTRACT = 2, 53 GOP_CHECK_CLIENT_ACCESS = 3, 54 GOP_CHECK_PAID = 4, 55 GOP_REDIRECT_TO_PAID_ORDER = 5, 56 GOP_HANDLE_UNPAID = 6, 57 GOP_CHECK_REFUNDED = 7, 58 GOP_RETURN_STATUS = 8, 59 GOP_RETURN_MHD_YES = 9, 60 GOP_RETURN_MHD_NO = 10 61 }; 62 63 64 /** 65 * Context for the operation. 66 */ 67 struct GetOrderData 68 { 69 70 /** 71 * Hashed version of contract terms. All zeros if not provided. 72 */ 73 struct TALER_PrivateContractHashP h_contract_terms; 74 75 /** 76 * Claim token used for access control. All zeros if not provided. 77 */ 78 struct TALER_ClaimTokenP claim_token; 79 80 /** 81 * DLL of (suspended) requests. 82 */ 83 struct GetOrderData *next; 84 85 /** 86 * DLL of (suspended) requests. 87 */ 88 struct GetOrderData *prev; 89 90 /** 91 * Context of the request. 92 */ 93 struct TMH_HandlerContext *hc; 94 95 /** 96 * Entry in the #resume_timeout_heap for this check payment, if we are 97 * suspended. 98 */ 99 struct TMH_SuspendedConnection sc; 100 101 /** 102 * Database event we are waiting on to be resuming on payment. 103 */ 104 struct GNUNET_DB_EventHandler *pay_eh; 105 106 /** 107 * Database event we are waiting on to be resuming for refunds. 108 */ 109 struct GNUNET_DB_EventHandler *refund_eh; 110 111 /** 112 * Database event we are waiting on to be resuming for repurchase 113 * detection updating some equivalent order (same fulfillment URL) 114 * to our session. 115 */ 116 struct GNUNET_DB_EventHandler *session_eh; 117 118 /** 119 * Which merchant instance is this for? 120 */ 121 struct MerchantInstance *mi; 122 123 /** 124 * order ID for the payment 125 */ 126 const char *order_id; 127 128 /** 129 * session of the client 130 */ 131 const char *session_id; 132 133 /** 134 * choice index (contract v1) 135 */ 136 int16_t choice_index; 137 138 /** 139 * Contract terms of the payment we are checking. NULL when they 140 * are not (yet) known. 141 */ 142 json_t *contract_terms_json; 143 144 /** 145 * Parsed contract terms, NULL when parsing failed. 146 */ 147 struct TALER_MERCHANT_Contract *contract_terms; 148 149 /** 150 * Total refunds granted for this payment. Only initialized 151 * if @e refunded is set to true. 152 */ 153 struct TALER_Amount refund_amount; 154 155 /** 156 * Total refunds already collected. 157 * if @e refunded is set to true. 158 */ 159 struct TALER_Amount refund_taken; 160 161 /** 162 * Phase in which we currently are handling this 163 * request. 164 */ 165 enum Phase phase; 166 167 /** 168 * Return code: #TALER_EC_NONE if successful. 169 */ 170 enum TALER_ErrorCode ec; 171 172 /** 173 * Did we suspend @a connection and are thus in 174 * the #god_head DLL (#GNUNET_YES). Set to 175 * #GNUNET_NO if we are not suspended, and to 176 * #GNUNET_SYSERR if we should close the connection 177 * without a response due to shutdown. 178 */ 179 enum GNUNET_GenericReturnValue suspended; 180 181 /** 182 * Set to YES if refunded orders should be included when 183 * doing repurchase detection. 184 */ 185 enum TALER_EXCHANGE_YesNoAll allow_refunded_for_repurchase; 186 187 /** 188 * Set to true if the client passed 'h_contract'. 189 */ 190 bool h_contract_provided; 191 192 /** 193 * Set to true if the client passed a 'claim' token. 194 */ 195 bool claim_token_provided; 196 197 /** 198 * Set to true if we are dealing with a claimed order 199 * (and thus @e h_contract_terms is set, otherwise certain 200 * DB queries will not work). 201 */ 202 bool claimed; 203 204 /** 205 * Set to true if this order was paid. 206 */ 207 bool paid; 208 209 /** 210 * Set to true if this order has been refunded and 211 * @e refund_amount is initialized. 212 */ 213 bool refunded; 214 215 /** 216 * Set to true if a refund is still available for the 217 * wallet for this payment. 218 * @deprecated: true if refund_taken < refund_amount 219 */ 220 bool refund_pending; 221 222 /** 223 * Set to true if the client requested HTML, otherwise we generate JSON. 224 */ 225 bool generate_html; 226 227 /** 228 * Did we parse the contract terms? 229 */ 230 bool contract_parsed; 231 232 /** 233 * Set to true if the refunds found in the DB have 234 * a different currency then the main contract. 235 */ 236 bool bad_refund_currency_in_db; 237 238 /** 239 * Did the hash of the contract match the contract 240 * hash supplied by the client? 241 */ 242 bool contract_match; 243 244 /** 245 * True if we had a claim token and the claim token 246 * provided by the client matched our claim token. 247 */ 248 bool token_match; 249 250 /** 251 * True if we found a (claimed) contract for the order, 252 * false if we had an unclaimed order. 253 */ 254 bool contract_available; 255 256 }; 257 258 259 /** 260 * Head of DLL of (suspended) requests. 261 */ 262 static struct GetOrderData *god_head; 263 264 /** 265 * Tail of DLL of (suspended) requests. 266 */ 267 static struct GetOrderData *god_tail; 268 269 270 void 271 TMH_force_wallet_get_order_resume (void) 272 { 273 struct GetOrderData *god; 274 275 while (NULL != (god = god_head)) 276 { 277 GNUNET_CONTAINER_DLL_remove (god_head, 278 god_tail, 279 god); 280 GNUNET_assert (god->suspended); 281 god->suspended = GNUNET_SYSERR; 282 MHD_resume_connection (god->sc.con); 283 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 284 } 285 } 286 287 288 /** 289 * Suspend this @a god until the trigger is satisfied. 290 * 291 * @param god request to suspend 292 */ 293 static void 294 suspend_god (struct GetOrderData *god) 295 { 296 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 297 "Suspending GET /orders/%s\n", 298 god->order_id); 299 /* We reset the contract terms and start by looking them up 300 again, as while we are suspended fundamental things could 301 change (such as the contract being claimed) */ 302 if (NULL != god->contract_terms_json) 303 { 304 json_decref (god->contract_terms_json); 305 god->contract_terms_json = NULL; 306 god->contract_parsed = false; 307 } 308 if (NULL != god->contract_terms) 309 { 310 TALER_MERCHANT_contract_free (god->contract_terms); 311 god->contract_terms = NULL; 312 } 313 GNUNET_assert (! god->suspended); 314 god->contract_parsed = false; 315 god->contract_match = false; 316 god->token_match = false; 317 god->contract_available = false; 318 god->phase = GOP_LOOKUP_TERMS; 319 god->suspended = GNUNET_YES; 320 GNUNET_CONTAINER_DLL_insert (god_head, 321 god_tail, 322 god); 323 MHD_suspend_connection (god->sc.con); 324 } 325 326 327 /** 328 * Clean up the session state for a GET /orders/$ID request. 329 * 330 * @param cls must be a `struct GetOrderData *` 331 */ 332 static void 333 god_cleanup (void *cls) 334 { 335 struct GetOrderData *god = cls; 336 337 if (NULL != god->contract_terms_json) 338 { 339 json_decref (god->contract_terms_json); 340 god->contract_terms_json = NULL; 341 } 342 if (NULL != god->contract_terms) 343 { 344 TALER_MERCHANT_contract_free (god->contract_terms); 345 god->contract_terms = NULL; 346 } 347 if (NULL != god->session_eh) 348 { 349 TMH_db->event_listen_cancel (god->session_eh); 350 god->session_eh = NULL; 351 } 352 if (NULL != god->refund_eh) 353 { 354 TMH_db->event_listen_cancel (god->refund_eh); 355 god->refund_eh = NULL; 356 } 357 if (NULL != god->pay_eh) 358 { 359 TMH_db->event_listen_cancel (god->pay_eh); 360 god->pay_eh = NULL; 361 } 362 GNUNET_free (god); 363 } 364 365 366 /** 367 * Finish the request by returning @a mret as the 368 * final result. 369 * 370 * @param[in,out] god request we are processing 371 * @param mret MHD result to return 372 */ 373 static void 374 phase_end (struct GetOrderData *god, 375 MHD_RESULT mret) 376 { 377 god->phase = (MHD_YES == mret) 378 ? GOP_RETURN_MHD_YES 379 : GOP_RETURN_MHD_NO; 380 } 381 382 383 /** 384 * Finish the request by returning an error @a ec 385 * with HTTP status @a http_status and @a message. 386 * 387 * @param[in,out] god request we are processing 388 * @param http_status HTTP status code to return 389 * @param ec error code to return 390 * @param message human readable hint to return, can be NULL 391 */ 392 static void 393 phase_fail (struct GetOrderData *god, 394 unsigned int http_status, 395 enum TALER_ErrorCode ec, 396 const char *message) 397 { 398 phase_end (god, 399 TALER_MHD_reply_with_error (god->sc.con, 400 http_status, 401 ec, 402 message)); 403 } 404 405 406 /** 407 * We have received a trigger from the database 408 * that we should (possibly) resume the request. 409 * 410 * @param cls a `struct GetOrderData` to resume 411 * @param extra string encoding refund amount (or NULL) 412 * @param extra_size number of bytes in @a extra 413 */ 414 static void 415 resume_by_event (void *cls, 416 const void *extra, 417 size_t extra_size) 418 { 419 struct GetOrderData *god = cls; 420 struct GNUNET_AsyncScopeSave old; 421 422 GNUNET_async_scope_enter (&god->hc->async_scope_id, 423 &old); 424 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 425 "Received event for %s with argument `%.*s`\n", 426 god->order_id, 427 (int) extra_size, 428 (const char *) extra); 429 if (! god->suspended) 430 { 431 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 432 "Not suspended, ignoring event\n"); 433 GNUNET_async_scope_restore (&old); 434 return; /* duplicate event is possible */ 435 } 436 if (GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout) && 437 god->sc.awaiting_refund) 438 { 439 char *as; 440 struct TALER_Amount a; 441 442 if (0 == extra_size) 443 { 444 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 445 "No amount given, but need refund above threshold\n"); 446 GNUNET_async_scope_restore (&old); 447 return; /* not relevant */ 448 } 449 as = GNUNET_strndup (extra, 450 extra_size); 451 if (GNUNET_OK != 452 TALER_string_to_amount (as, 453 &a)) 454 { 455 GNUNET_break (0); 456 GNUNET_async_scope_restore (&old); 457 GNUNET_free (as); 458 return; 459 } 460 GNUNET_free (as); 461 if (GNUNET_OK != 462 TALER_amount_cmp_currency (&god->sc.refund_expected, 463 &a)) 464 { 465 GNUNET_break (0); 466 GNUNET_async_scope_restore (&old); 467 return; /* bad currency!? */ 468 } 469 if (1 == TALER_amount_cmp (&god->sc.refund_expected, 470 &a)) 471 { 472 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 473 "Amount too small to trigger resuming\n"); 474 GNUNET_async_scope_restore (&old); 475 return; /* refund too small */ 476 } 477 } 478 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 479 "Resuming (%s/%s) by event with argument `%.*s`\n", 480 GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout) 481 ? "future" 482 : "past", 483 god->sc.awaiting_refund 484 ? "awaiting refund" 485 : "not waiting for refund", 486 (int) extra_size, 487 (const char *) extra); 488 god->suspended = GNUNET_NO; 489 GNUNET_CONTAINER_DLL_remove (god_head, 490 god_tail, 491 god); 492 MHD_resume_connection (god->sc.con); 493 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 494 GNUNET_async_scope_restore (&old); 495 } 496 497 498 /** 499 * First phase (after request parsing). 500 * Set up long-polling. 501 * 502 * @param[in,out] god request context 503 */ 504 static void 505 phase_init (struct GetOrderData *god) 506 { 507 god->phase++; 508 if (god->generate_html) 509 return; /* If HTML is requested, we never actually long poll. */ 510 if (! GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout)) 511 return; /* long polling not requested */ 512 513 if (god->sc.awaiting_refund || 514 god->sc.awaiting_refund_obtained) 515 { 516 struct TMH_OrderPayEventP refund_eh = { 517 .header.size = htons (sizeof (refund_eh)), 518 .header.type = htons (god->sc.awaiting_refund_obtained 519 ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED 520 : TALER_DBEVENT_MERCHANT_ORDER_REFUND), 521 .merchant_pub = god->hc->instance->merchant_pub 522 }; 523 524 GNUNET_CRYPTO_hash (god->order_id, 525 strlen (god->order_id), 526 &refund_eh.h_order_id); 527 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 528 "Subscribing %p to refunds on %s\n", 529 god, 530 god->order_id); 531 god->refund_eh 532 = TMH_db->event_listen ( 533 TMH_db->cls, 534 &refund_eh.header, 535 GNUNET_TIME_absolute_get_remaining ( 536 god->sc.long_poll_timeout), 537 &resume_by_event, 538 god); 539 } 540 { 541 struct TMH_OrderPayEventP pay_eh = { 542 .header.size = htons (sizeof (pay_eh)), 543 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID), 544 .merchant_pub = god->hc->instance->merchant_pub 545 }; 546 547 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 548 "Subscribing to payments on %s\n", 549 god->order_id); 550 GNUNET_CRYPTO_hash (god->order_id, 551 strlen (god->order_id), 552 &pay_eh.h_order_id); 553 god->pay_eh 554 = TMH_db->event_listen ( 555 TMH_db->cls, 556 &pay_eh.header, 557 GNUNET_TIME_absolute_get_remaining ( 558 god->sc.long_poll_timeout), 559 &resume_by_event, 560 god); 561 } 562 } 563 564 565 /** 566 * Lookup contract terms and check client has the 567 * right to access this order (by claim token or 568 * contract hash). 569 * 570 * @param[in,out] god request context 571 */ 572 static void 573 phase_lookup_terms (struct GetOrderData *god) 574 { 575 uint64_t order_serial; 576 struct TALER_ClaimTokenP db_claim_token; 577 578 /* Convert order_id to h_contract_terms */ 579 TMH_db->preflight (TMH_db->cls); 580 GNUNET_assert (NULL == god->contract_terms_json); 581 582 { 583 enum GNUNET_DB_QueryStatus qs; 584 585 bool paid; 586 bool wired; 587 bool session_matches; 588 qs = TMH_db->lookup_contract_terms3 ( 589 TMH_db->cls, 590 god->hc->instance->settings.id, 591 god->order_id, 592 NULL, 593 &god->contract_terms_json, 594 &order_serial, 595 &paid, 596 &wired, 597 &session_matches, 598 &db_claim_token, 599 &god->choice_index); 600 if (0 > qs) 601 { 602 /* single, read-only SQL statements should never cause 603 serialization problems */ 604 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 605 /* Always report on hard error as well to enable diagnostics */ 606 GNUNET_break (0); 607 phase_fail (god, 608 MHD_HTTP_INTERNAL_SERVER_ERROR, 609 TALER_EC_GENERIC_DB_FETCH_FAILED, 610 "lookup_contract_terms"); 611 return; 612 } 613 /* Note: when "!ord.requireClaimToken" and the client does not provide 614 a claim token (all zeros!), then token_match==TRUE below: */ 615 god->token_match 616 = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 617 && (0 == GNUNET_memcmp (&db_claim_token, 618 &god->claim_token)); 619 } 620 621 /* Check if client provided the right hash code of the contract terms */ 622 if (NULL != god->contract_terms_json) 623 { 624 god->contract_available = true; 625 if (GNUNET_YES == 626 GNUNET_is_zero (&god->h_contract_terms)) 627 { 628 if (GNUNET_OK != 629 TALER_JSON_contract_hash (god->contract_terms_json, 630 &god->h_contract_terms)) 631 { 632 GNUNET_break (0); 633 phase_fail (god, 634 MHD_HTTP_INTERNAL_SERVER_ERROR, 635 TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, 636 "contract terms"); 637 return; 638 } 639 } 640 else 641 { 642 struct TALER_PrivateContractHashP h; 643 644 if (GNUNET_OK != 645 TALER_JSON_contract_hash (god->contract_terms_json, 646 &h)) 647 { 648 GNUNET_break (0); 649 phase_fail (god, 650 MHD_HTTP_INTERNAL_SERVER_ERROR, 651 TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, 652 "contract terms"); 653 return; 654 } 655 god->contract_match = (0 == 656 GNUNET_memcmp (&h, 657 &god->h_contract_terms)); 658 if (! god->contract_match) 659 { 660 GNUNET_break_op (0); 661 phase_fail (god, 662 MHD_HTTP_FORBIDDEN, 663 TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER, 664 NULL); 665 return; 666 } 667 } 668 } 669 670 if (god->contract_available) 671 { 672 god->claimed = true; 673 } 674 else 675 { 676 struct TALER_MerchantPostDataHashP unused; 677 enum GNUNET_DB_QueryStatus qs; 678 679 qs = TMH_db->lookup_order ( 680 TMH_db->cls, 681 god->hc->instance->settings.id, 682 god->order_id, 683 &db_claim_token, 684 &unused, 685 (NULL == god->contract_terms_json) 686 ? &god->contract_terms_json 687 : NULL); 688 if (0 > qs) 689 { 690 /* single, read-only SQL statements should never cause 691 serialization problems */ 692 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 693 /* Always report on hard error as well to enable diagnostics */ 694 GNUNET_break (0); 695 phase_fail (god, 696 MHD_HTTP_INTERNAL_SERVER_ERROR, 697 TALER_EC_GENERIC_DB_FETCH_FAILED, 698 "lookup_order"); 699 return; 700 } 701 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 702 { 703 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 704 "Unknown order id given: `%s'\n", 705 god->order_id); 706 phase_fail (god, 707 MHD_HTTP_NOT_FOUND, 708 TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN, 709 god->order_id); 710 return; 711 } 712 /* Note: when "!ord.requireClaimToken" and the client does not provide 713 a claim token (all zeros!), then token_match==TRUE below: */ 714 god->token_match 715 = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && 716 (0 == GNUNET_memcmp (&db_claim_token, 717 &god->claim_token)); 718 } /* end unclaimed order logic */ 719 god->phase++; 720 } 721 722 723 /** 724 * Parse contract terms. 725 * 726 * @param[in,out] god request context 727 */ 728 static void 729 phase_parse_contract (struct GetOrderData *god) 730 { 731 GNUNET_break (NULL == god->contract_terms); 732 god->contract_terms = TALER_MERCHANT_contract_parse ( 733 god->contract_terms_json, 734 true); 735 736 if (NULL == god->contract_terms) 737 { 738 phase_fail (god, 739 MHD_HTTP_INTERNAL_SERVER_ERROR, 740 TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID, 741 god->order_id); 742 return; 743 } 744 god->contract_parsed = true; 745 if ( (NULL != god->session_id) && 746 (NULL != god->contract_terms->fulfillment_url) && 747 (NULL == god->session_eh) ) 748 { 749 struct TMH_SessionEventP session_eh = { 750 .header.size = htons (sizeof (session_eh)), 751 .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED), 752 .merchant_pub = god->hc->instance->merchant_pub 753 }; 754 755 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 756 "Subscribing to session triggers for %p\n", 757 god); 758 GNUNET_CRYPTO_hash (god->session_id, 759 strlen (god->session_id), 760 &session_eh.h_session_id); 761 GNUNET_CRYPTO_hash (god->contract_terms->fulfillment_url, 762 strlen (god->contract_terms->fulfillment_url), 763 &session_eh.h_fulfillment_url); 764 god->session_eh 765 = TMH_db->event_listen ( 766 TMH_db->cls, 767 &session_eh.header, 768 GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout), 769 &resume_by_event, 770 god); 771 } 772 god->phase++; 773 } 774 775 776 /** 777 * Check that this order is unclaimed or claimed by 778 * this client. 779 * 780 * @param[in,out] god request context 781 */ 782 static void 783 phase_check_client_access (struct GetOrderData *god) 784 { 785 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 786 "Token match: %d, contract_available: %d, contract match: %d, claimed: %d\n", 787 god->token_match, 788 god->contract_available, 789 god->contract_match, 790 god->claimed); 791 792 if (god->claim_token_provided && ! god->token_match) 793 { 794 /* Authentication provided but wrong. */ 795 GNUNET_break_op (0); 796 phase_fail (god, 797 MHD_HTTP_FORBIDDEN, 798 TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN, 799 "authentication with claim token provided but wrong"); 800 return; 801 } 802 803 if (god->h_contract_provided && ! god->contract_match) 804 { 805 /* Authentication provided but wrong. */ 806 GNUNET_break_op (0); 807 phase_fail (god, 808 MHD_HTTP_FORBIDDEN, 809 TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH, 810 NULL); 811 return; 812 } 813 814 if (! (god->token_match || 815 god->contract_match) ) 816 { 817 818 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 819 "Neither claim token nor contract matched\n"); 820 /* Client has no rights to this order */ 821 if (NULL == god->contract_terms->public_reorder_url) 822 { 823 /* We cannot give the client a new order, just fail */ 824 if (! GNUNET_is_zero (&god->h_contract_terms)) 825 { 826 GNUNET_break_op (0); 827 phase_fail (god, 828 MHD_HTTP_FORBIDDEN, 829 TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER, 830 NULL); 831 return; 832 } 833 GNUNET_break_op (0); 834 phase_fail (god, 835 MHD_HTTP_FORBIDDEN, 836 TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN, 837 "no 'public_reorder_url'"); 838 return; 839 } 840 /* We have a fulfillment URL, redirect the client there, maybe 841 the frontend can generate a fresh order for this new customer */ 842 if (god->generate_html) 843 { 844 /* Contract was claimed (maybe by another device), so this client 845 cannot get the status information. Redirect to fulfillment page, 846 where the client may be able to pickup a fresh order -- or might 847 be able authenticate via session ID */ 848 struct MHD_Response *reply; 849 MHD_RESULT ret; 850 851 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 852 "Contract claimed, redirecting to fulfillment page for order %s\n", 853 god->order_id); 854 reply = MHD_create_response_from_buffer (0, 855 NULL, 856 MHD_RESPMEM_PERSISTENT); 857 if (NULL == reply) 858 { 859 GNUNET_break (0); 860 phase_end (god, 861 MHD_NO); 862 return; 863 } 864 GNUNET_break (MHD_YES == 865 MHD_add_response_header ( 866 reply, 867 MHD_HTTP_HEADER_LOCATION, 868 god->contract_terms->public_reorder_url)); 869 ret = MHD_queue_response (god->sc.con, 870 MHD_HTTP_FOUND, 871 reply); 872 MHD_destroy_response (reply); 873 phase_end (god, 874 ret); 875 return; 876 } 877 /* Need to generate JSON reply */ 878 phase_end (god, 879 TALER_MHD_REPLY_JSON_PACK ( 880 god->sc.con, 881 MHD_HTTP_ACCEPTED, 882 GNUNET_JSON_pack_string ( 883 "public_reorder_url", 884 god->contract_terms->public_reorder_url))); 885 return; 886 } 887 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 888 "Claim token or contract matched\n"); 889 god->phase++; 890 } 891 892 893 /** 894 * Return the order summary of the contract of @a god in the 895 * preferred language of the HTTP client. 896 * 897 * @param god order to extract summary from 898 * @return dummy error message summary if no summary was provided in the contract 899 */ 900 static const char * 901 get_order_summary (const struct GetOrderData *god) 902 { 903 const char *language_pattern; 904 const char *ret; 905 906 language_pattern = MHD_lookup_connection_value (god->sc.con, 907 MHD_HEADER_KIND, 908 MHD_HTTP_HEADER_ACCEPT_LANGUAGE); 909 if (NULL == language_pattern) 910 language_pattern = "en"; 911 ret = json_string_value (TALER_JSON_extract_i18n (god->contract_terms_json, 912 language_pattern, 913 "summary")); 914 if (NULL == ret) 915 { 916 /* Upon order creation (and insertion into the database), the presence 917 of a summary should have been checked. So if we get here, someone 918 did something fishy to our database... */ 919 GNUNET_break (0); 920 ret = "<bug: no summary>"; 921 } 922 return ret; 923 } 924 925 926 /** 927 * The client did not yet pay, send it the payment request. 928 * 929 * @param god check pay request context 930 * @param already_paid_order_id if for the fulfillment URI there is 931 * already a paid order, this is the order ID to redirect 932 * the wallet to; NULL if not applicable 933 * @return true to exit due to suspension 934 */ 935 static bool 936 send_pay_request (struct GetOrderData *god, 937 const char *already_paid_order_id) 938 { 939 MHD_RESULT ret; 940 char *taler_pay_uri; 941 char *order_status_url; 942 struct GNUNET_TIME_Relative remaining; 943 944 remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout); 945 if ( (! GNUNET_TIME_relative_is_zero (remaining)) && 946 (NULL == already_paid_order_id) ) 947 { 948 /* long polling: do not queue a response, suspend connection instead */ 949 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 950 "Suspending request: long polling for payment\n"); 951 suspend_god (god); 952 return true; 953 } 954 955 /* Check if resource_id has been paid for in the same session 956 * with another order_id. 957 */ 958 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 959 "Sending payment request\n"); 960 taler_pay_uri = TMH_make_taler_pay_uri ( 961 god->sc.con, 962 god->order_id, 963 god->session_id, 964 god->hc->instance->settings.id, 965 &god->claim_token); 966 order_status_url = TMH_make_order_status_url ( 967 god->sc.con, 968 god->order_id, 969 god->session_id, 970 god->hc->instance->settings.id, 971 &god->claim_token, 972 NULL); 973 if ( (NULL == taler_pay_uri) || 974 (NULL == order_status_url) ) 975 { 976 GNUNET_break_op (0); 977 GNUNET_free (taler_pay_uri); 978 GNUNET_free (order_status_url); 979 phase_fail (god, 980 MHD_HTTP_BAD_REQUEST, 981 TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED, 982 "host"); 983 return false; 984 } 985 if (god->generate_html) 986 { 987 if (NULL != already_paid_order_id) 988 { 989 struct MHD_Response *reply; 990 991 GNUNET_assert (NULL != god->contract_terms->fulfillment_url); 992 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 993 "Redirecting to already paid order %s via fulfillment URL %s\n", 994 already_paid_order_id, 995 god->contract_terms->fulfillment_url); 996 reply = MHD_create_response_from_buffer (0, 997 NULL, 998 MHD_RESPMEM_PERSISTENT); 999 if (NULL == reply) 1000 { 1001 GNUNET_break (0); 1002 phase_end (god, 1003 MHD_NO); 1004 return false; 1005 } 1006 GNUNET_break (MHD_YES == 1007 MHD_add_response_header ( 1008 reply, 1009 MHD_HTTP_HEADER_LOCATION, 1010 god->contract_terms->fulfillment_url)); 1011 { 1012 ret = MHD_queue_response (god->sc.con, 1013 MHD_HTTP_FOUND, 1014 reply); 1015 MHD_destroy_response (reply); 1016 phase_end (god, 1017 ret); 1018 return false; 1019 } 1020 } 1021 1022 { 1023 char *qr; 1024 1025 qr = TMH_create_qrcode (taler_pay_uri); 1026 if (NULL == qr) 1027 { 1028 GNUNET_break (0); 1029 phase_end (god, 1030 MHD_NO); 1031 return false; 1032 } 1033 { 1034 enum GNUNET_GenericReturnValue res; 1035 json_t *context; 1036 1037 context = GNUNET_JSON_PACK ( 1038 GNUNET_JSON_pack_string ("taler_pay_uri", 1039 taler_pay_uri), 1040 GNUNET_JSON_pack_string ("order_status_url", 1041 order_status_url), 1042 GNUNET_JSON_pack_string ("taler_pay_qrcode_svg", 1043 qr), 1044 GNUNET_JSON_pack_string ("order_summary", 1045 get_order_summary (god))); 1046 res = TALER_TEMPLATING_reply ( 1047 god->sc.con, 1048 MHD_HTTP_PAYMENT_REQUIRED, 1049 "request_payment", 1050 god->hc->instance->settings.id, 1051 taler_pay_uri, 1052 context); 1053 if (GNUNET_SYSERR == res) 1054 { 1055 GNUNET_break (0); 1056 ret = MHD_NO; 1057 } 1058 else 1059 { 1060 ret = MHD_YES; 1061 } 1062 json_decref (context); 1063 } 1064 GNUNET_free (qr); 1065 } 1066 } 1067 else /* end of 'generate HTML' */ 1068 { 1069 ret = TALER_MHD_REPLY_JSON_PACK ( 1070 god->sc.con, 1071 MHD_HTTP_PAYMENT_REQUIRED, 1072 GNUNET_JSON_pack_string ("taler_pay_uri", 1073 taler_pay_uri), 1074 GNUNET_JSON_pack_allow_null ( 1075 GNUNET_JSON_pack_string ("fulfillment_url", 1076 god->contract_terms->fulfillment_url)), 1077 GNUNET_JSON_pack_allow_null ( 1078 GNUNET_JSON_pack_string ("already_paid_order_id", 1079 already_paid_order_id))); 1080 } 1081 GNUNET_free (taler_pay_uri); 1082 GNUNET_free (order_status_url); 1083 phase_end (god, 1084 ret); 1085 return false; 1086 } 1087 1088 1089 /** 1090 * Check if the order has been paid. 1091 * 1092 * @param[in,out] god request context 1093 */ 1094 static void 1095 phase_check_paid (struct GetOrderData *god) 1096 { 1097 enum GNUNET_DB_QueryStatus qs; 1098 struct TALER_PrivateContractHashP h_contract; 1099 1100 god->paid = false; 1101 qs = TMH_db->lookup_order_status ( 1102 TMH_db->cls, 1103 god->hc->instance->settings.id, 1104 god->order_id, 1105 &h_contract, 1106 &god->paid); 1107 if (0 > qs) 1108 { 1109 /* Always report on hard error as well to enable diagnostics */ 1110 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 1111 phase_fail (god, 1112 MHD_HTTP_INTERNAL_SERVER_ERROR, 1113 TALER_EC_GENERIC_DB_FETCH_FAILED, 1114 "lookup_order_status"); 1115 return; 1116 } 1117 god->phase++; 1118 } 1119 1120 1121 /** 1122 * Check if the client already paid for an equivalent 1123 * order under this session, and if so redirect to 1124 * that order. 1125 * 1126 * @param[in,out] god request context 1127 * @return true to exit due to suspension 1128 */ 1129 static bool 1130 phase_redirect_to_paid_order (struct GetOrderData *god) 1131 { 1132 if ( (NULL != god->session_id) && 1133 (NULL != god->contract_terms->fulfillment_url) ) 1134 { 1135 /* Check if client paid for this fulfillment article 1136 already within this session, but using a different 1137 order ID. If so, redirect the client to the order 1138 it already paid. Allows, for example, the case 1139 where a mobile phone pays for a browser's session, 1140 where the mobile phone has a different order 1141 ID (because it purchased the article earlier) 1142 than the one that the browser is waiting for. */ 1143 char *already_paid_order_id = NULL; 1144 enum GNUNET_DB_QueryStatus qs; 1145 1146 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1147 "Running re-purchase detection for %s/%s\n", 1148 god->session_id, 1149 god->contract_terms->fulfillment_url); 1150 qs = TMH_db->lookup_order_by_fulfillment ( 1151 TMH_db->cls, 1152 god->hc->instance->settings.id, 1153 god->contract_terms->fulfillment_url, 1154 god->session_id, 1155 TALER_EXCHANGE_YNA_NO != god->allow_refunded_for_repurchase, 1156 &already_paid_order_id); 1157 if (qs < 0) 1158 { 1159 /* single, read-only SQL statements should never cause 1160 serialization problems */ 1161 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 1162 /* Always report on hard error as well to enable diagnostics */ 1163 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 1164 phase_fail (god, 1165 MHD_HTTP_INTERNAL_SERVER_ERROR, 1166 TALER_EC_GENERIC_DB_FETCH_FAILED, 1167 "order by fulfillment"); 1168 return false; 1169 } 1170 if ( (! god->paid) && 1171 ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || 1172 (0 != strcmp (god->order_id, 1173 already_paid_order_id)) ) ) 1174 { 1175 bool ret; 1176 1177 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1178 "Sending pay request for order %s (already paid: %s)\n", 1179 god->order_id, 1180 already_paid_order_id); 1181 ret = send_pay_request (god, 1182 already_paid_order_id); 1183 GNUNET_free (already_paid_order_id); 1184 return ret; 1185 } 1186 GNUNET_free (already_paid_order_id); 1187 } 1188 god->phase++; 1189 return false; 1190 } 1191 1192 1193 /** 1194 * Check if the order has been paid, and if not 1195 * request payment. 1196 * 1197 * @param[in,out] god request context 1198 * @return true to exit due to suspension 1199 */ 1200 static bool 1201 phase_handle_unpaid (struct GetOrderData *god) 1202 { 1203 if (god->paid) 1204 { 1205 god->phase++; 1206 return false; 1207 } 1208 if (god->claimed) 1209 { 1210 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1211 "Order claimed but unpaid, sending pay request for order %s\n", 1212 god->order_id); 1213 } 1214 else 1215 { 1216 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1217 "Order unclaimed, sending pay request for order %s\n", 1218 god->order_id); 1219 } 1220 return send_pay_request (god, 1221 NULL); 1222 } 1223 1224 1225 /** 1226 * Function called with detailed information about a refund. 1227 * It is responsible for packing up the data to return. 1228 * 1229 * @param cls closure 1230 * @param refund_serial unique serial number of the refund 1231 * @param timestamp time of the refund (for grouping of refunds in the wallet UI) 1232 * @param coin_pub public coin from which the refund comes from 1233 * @param exchange_url URL of the exchange that issued @a coin_pub 1234 * @param rtransaction_id identificator of the refund 1235 * @param reason human-readable explanation of the refund 1236 * @param refund_amount refund amount which is being taken from @a coin_pub 1237 * @param pending true if the this refund was not yet processed by the wallet/exchange 1238 */ 1239 static void 1240 process_refunds_cb (void *cls, 1241 uint64_t refund_serial, 1242 struct GNUNET_TIME_Timestamp timestamp, 1243 const struct TALER_CoinSpendPublicKeyP *coin_pub, 1244 const char *exchange_url, 1245 uint64_t rtransaction_id, 1246 const char *reason, 1247 const struct TALER_Amount *refund_amount, 1248 bool pending) 1249 { 1250 struct GetOrderData *god = cls; 1251 1252 (void) refund_serial; 1253 (void) timestamp; 1254 (void) exchange_url; 1255 (void) rtransaction_id; 1256 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1257 "Found refund of %s for coin %s with reason `%s' in database\n", 1258 TALER_amount2s (refund_amount), 1259 TALER_B2S (coin_pub), 1260 reason); 1261 god->refund_pending |= pending; 1262 if ( (GNUNET_OK != 1263 TALER_amount_cmp_currency (&god->refund_taken, 1264 refund_amount)) || 1265 (GNUNET_OK != 1266 TALER_amount_cmp_currency (&god->refund_amount, 1267 refund_amount)) ) 1268 { 1269 god->bad_refund_currency_in_db = true; 1270 return; 1271 } 1272 if (! pending) 1273 { 1274 GNUNET_assert (0 <= 1275 TALER_amount_add (&god->refund_taken, 1276 &god->refund_taken, 1277 refund_amount)); 1278 } 1279 GNUNET_assert (0 <= 1280 TALER_amount_add (&god->refund_amount, 1281 &god->refund_amount, 1282 refund_amount)); 1283 god->refunded = true; 1284 } 1285 1286 1287 /** 1288 * Check if the order has been refunded. 1289 * 1290 * @param[in,out] god request context 1291 * @return true to exit due to suspension 1292 */ 1293 static bool 1294 phase_check_refunded (struct GetOrderData *god) 1295 { 1296 enum GNUNET_DB_QueryStatus qs; 1297 struct TALER_Amount refund_amount; 1298 const char *refund_currency; 1299 1300 switch (god->contract_terms->version) 1301 { 1302 case TALER_MERCHANT_CONTRACT_VERSION_0: 1303 refund_amount = god->contract_terms->details.v0.brutto; 1304 refund_currency = god->contract_terms->details.v0.brutto.currency; 1305 break; 1306 case TALER_MERCHANT_CONTRACT_VERSION_1: 1307 if (god->choice_index < 0) 1308 { 1309 // order was not paid, no refund to be checked 1310 god->phase++; 1311 return false; 1312 } 1313 GNUNET_assert (god->choice_index < 1314 god->contract_terms->details.v1.choices_len); 1315 refund_currency = god->contract_terms->details.v1.choices[god->choice_index] 1316 .amount.currency; 1317 GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (refund_currency, 1318 &refund_amount)); 1319 break; 1320 default: 1321 { 1322 GNUNET_break (0); 1323 phase_fail (god, 1324 MHD_HTTP_INTERNAL_SERVER_ERROR, 1325 TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION, 1326 NULL); 1327 return false; 1328 } 1329 } 1330 1331 if ( (god->sc.awaiting_refund) && 1332 (GNUNET_OK != 1333 TALER_amount_cmp_currency (&refund_amount, 1334 &god->sc.refund_expected)) ) 1335 { 1336 GNUNET_break (0); 1337 phase_fail (god, 1338 MHD_HTTP_CONFLICT, 1339 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1340 refund_currency); 1341 return false; 1342 } 1343 1344 /* At this point, we know the contract was paid. Let's check for 1345 refunds. First, clear away refunds found from previous invocations. */ 1346 GNUNET_assert (GNUNET_OK == 1347 TALER_amount_set_zero (refund_currency, 1348 &god->refund_amount)); 1349 GNUNET_assert (GNUNET_OK == 1350 TALER_amount_set_zero (refund_currency, 1351 &god->refund_taken)); 1352 qs = TMH_db->lookup_refunds_detailed ( 1353 TMH_db->cls, 1354 god->hc->instance->settings.id, 1355 &god->h_contract_terms, 1356 &process_refunds_cb, 1357 god); 1358 if (0 > qs) 1359 { 1360 GNUNET_break (0); 1361 phase_fail (god, 1362 MHD_HTTP_INTERNAL_SERVER_ERROR, 1363 TALER_EC_GENERIC_DB_FETCH_FAILED, 1364 "lookup_refunds_detailed"); 1365 return false; 1366 } 1367 if (god->bad_refund_currency_in_db) 1368 { 1369 GNUNET_break (0); 1370 phase_fail (god, 1371 MHD_HTTP_INTERNAL_SERVER_ERROR, 1372 TALER_EC_GENERIC_DB_FETCH_FAILED, 1373 "currency mix-up between contract price and refunds in database"); 1374 return false; 1375 } 1376 if ( ((god->sc.awaiting_refund) && 1377 ( (! god->refunded) || 1378 (1 != TALER_amount_cmp (&god->refund_amount, 1379 &god->sc.refund_expected)) )) || 1380 ( (god->sc.awaiting_refund_obtained) && 1381 (god->refund_pending) ) ) 1382 { 1383 /* Client is waiting for a refund larger than what we have, suspend 1384 until timeout */ 1385 struct GNUNET_TIME_Relative remaining; 1386 1387 remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout); 1388 if ( (! GNUNET_TIME_relative_is_zero (remaining)) && 1389 (! god->generate_html) ) 1390 { 1391 /* yes, indeed suspend */ 1392 if (god->sc.awaiting_refund) 1393 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1394 "Awaiting refund exceeding %s\n", 1395 TALER_amount2s (&god->sc.refund_expected)); 1396 if (god->sc.awaiting_refund_obtained) 1397 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1398 "Awaiting pending refunds\n"); 1399 suspend_god (god); 1400 return true; 1401 } 1402 } 1403 god->phase++; 1404 return false; 1405 } 1406 1407 1408 /** 1409 * Create a taler://refund/ URI for the given @a con and @a order_id 1410 * and @a instance_id. 1411 * 1412 * @param merchant_base_url URL to take host and path from; 1413 * we cannot take it from the MHD connection as a browser 1414 * may have changed 'http' to 'https' and we MUST be consistent 1415 * with what the merchant's frontend used initially 1416 * @param order_id the order id 1417 * @return corresponding taler://refund/ URI, or NULL on missing "host" 1418 */ 1419 static char * 1420 make_taler_refund_uri (const char *merchant_base_url, 1421 const char *order_id) 1422 { 1423 struct GNUNET_Buffer buf = { 0 }; 1424 char *url; 1425 struct GNUNET_Uri uri; 1426 1427 url = GNUNET_strdup (merchant_base_url); 1428 if (-1 == GNUNET_uri_parse (&uri, 1429 url)) 1430 { 1431 GNUNET_break (0); 1432 GNUNET_free (url); 1433 return NULL; 1434 } 1435 GNUNET_assert (NULL != order_id); 1436 GNUNET_buffer_write_str (&buf, 1437 "taler"); 1438 if (0 == strcasecmp ("http", 1439 uri.scheme)) 1440 GNUNET_buffer_write_str (&buf, 1441 "+http"); 1442 GNUNET_buffer_write_str (&buf, 1443 "://refund/"); 1444 GNUNET_buffer_write_str (&buf, 1445 uri.host); 1446 if (0 != uri.port) 1447 GNUNET_buffer_write_fstr (&buf, 1448 ":%u", 1449 (unsigned int) uri.port); 1450 if (NULL != uri.path) 1451 GNUNET_buffer_write_path (&buf, 1452 uri.path); 1453 GNUNET_buffer_write_path (&buf, 1454 order_id); 1455 GNUNET_buffer_write_path (&buf, 1456 ""); // Trailing slash 1457 GNUNET_free (url); 1458 return GNUNET_buffer_reap_str (&buf); 1459 } 1460 1461 1462 /** 1463 * Generate the order status response. 1464 * 1465 * @param[in,out] god request context 1466 */ 1467 static void 1468 phase_return_status (struct GetOrderData *god) 1469 { 1470 /* All operations done, build final response */ 1471 if (! god->generate_html) 1472 { 1473 phase_end (god, 1474 TALER_MHD_REPLY_JSON_PACK ( 1475 god->sc.con, 1476 MHD_HTTP_OK, 1477 GNUNET_JSON_pack_allow_null ( 1478 GNUNET_JSON_pack_string ("fulfillment_url", 1479 god->contract_terms->fulfillment_url 1480 )), 1481 GNUNET_JSON_pack_bool ("refunded", 1482 god->refunded), 1483 GNUNET_JSON_pack_bool ("refund_pending", 1484 god->refund_pending), 1485 TALER_JSON_pack_amount ("refund_taken", 1486 &god->refund_taken), 1487 TALER_JSON_pack_amount ("refund_amount", 1488 &god->refund_amount))); 1489 return; 1490 } 1491 1492 if (god->refund_pending) 1493 { 1494 char *qr; 1495 char *uri; 1496 1497 GNUNET_assert (NULL != god->contract_terms_json); 1498 uri = make_taler_refund_uri (god->contract_terms->merchant_base_url, 1499 god->order_id); 1500 if (NULL == uri) 1501 { 1502 GNUNET_break (0); 1503 phase_fail (god, 1504 MHD_HTTP_INTERNAL_SERVER_ERROR, 1505 TALER_EC_GENERIC_ALLOCATION_FAILURE, 1506 "refund URI"); 1507 return; 1508 } 1509 qr = TMH_create_qrcode (uri); 1510 if (NULL == qr) 1511 { 1512 GNUNET_break (0); 1513 GNUNET_free (uri); 1514 phase_fail (god, 1515 MHD_HTTP_INTERNAL_SERVER_ERROR, 1516 TALER_EC_GENERIC_ALLOCATION_FAILURE, 1517 "qr code"); 1518 return; 1519 } 1520 1521 { 1522 enum GNUNET_GenericReturnValue res; 1523 json_t *context; 1524 1525 context = GNUNET_JSON_PACK ( 1526 GNUNET_JSON_pack_string ("order_summary", 1527 get_order_summary (god)), 1528 TALER_JSON_pack_amount ("refund_amount", 1529 &god->refund_amount), 1530 TALER_JSON_pack_amount ("refund_taken", 1531 &god->refund_taken), 1532 GNUNET_JSON_pack_string ("taler_refund_uri", 1533 uri), 1534 GNUNET_JSON_pack_string ("taler_refund_qrcode_svg", 1535 qr)); 1536 res = TALER_TEMPLATING_reply ( 1537 god->sc.con, 1538 MHD_HTTP_OK, 1539 "offer_refund", 1540 god->hc->instance->settings.id, 1541 uri, 1542 context); 1543 GNUNET_break (GNUNET_OK == res); 1544 json_decref (context); 1545 phase_end (god, 1546 (GNUNET_SYSERR == res) 1547 ? MHD_NO 1548 : MHD_YES); 1549 } 1550 GNUNET_free (uri); 1551 GNUNET_free (qr); 1552 return; 1553 } 1554 1555 { 1556 enum GNUNET_GenericReturnValue res; 1557 json_t *context; 1558 1559 context = GNUNET_JSON_PACK ( 1560 GNUNET_JSON_pack_object_incref ("contract_terms", 1561 god->contract_terms_json), 1562 GNUNET_JSON_pack_string ("order_summary", 1563 get_order_summary (god)), 1564 TALER_JSON_pack_amount ("refund_amount", 1565 &god->refund_amount), 1566 TALER_JSON_pack_amount ("refund_taken", 1567 &god->refund_taken)); 1568 res = TALER_TEMPLATING_reply ( 1569 god->sc.con, 1570 MHD_HTTP_OK, 1571 "show_order_details", 1572 god->hc->instance->settings.id, 1573 NULL, 1574 context); 1575 GNUNET_break (GNUNET_OK == res); 1576 json_decref (context); 1577 phase_end (god, 1578 (GNUNET_SYSERR == res) 1579 ? MHD_NO 1580 : MHD_YES); 1581 } 1582 } 1583 1584 1585 MHD_RESULT 1586 TMH_get_orders_ID (const struct TMH_RequestHandler *rh, 1587 struct MHD_Connection *connection, 1588 struct TMH_HandlerContext *hc) 1589 { 1590 struct GetOrderData *god = hc->ctx; 1591 1592 (void) rh; 1593 if (NULL == god) 1594 { 1595 god = GNUNET_new (struct GetOrderData); 1596 hc->ctx = god; 1597 hc->cc = &god_cleanup; 1598 god->sc.con = connection; 1599 god->hc = hc; 1600 god->order_id = hc->infix; 1601 god->generate_html 1602 = TMH_MHD_test_html_desired (connection); 1603 1604 /* first-time initialization / sanity checks */ 1605 TALER_MHD_parse_request_arg_auto (connection, 1606 "h_contract", 1607 &god->h_contract_terms, 1608 god->h_contract_provided); 1609 TALER_MHD_parse_request_arg_auto (connection, 1610 "token", 1611 &god->claim_token, 1612 god->claim_token_provided); 1613 if (! (TALER_MHD_arg_to_yna (connection, 1614 "allow_refunded_for_repurchase", 1615 TALER_EXCHANGE_YNA_NO, 1616 &god->allow_refunded_for_repurchase)) ) 1617 return TALER_MHD_reply_with_error (connection, 1618 MHD_HTTP_BAD_REQUEST, 1619 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1620 "allow_refunded_for_repurchase"); 1621 god->session_id = MHD_lookup_connection_value (connection, 1622 MHD_GET_ARGUMENT_KIND, 1623 "session_id"); 1624 1625 /* process await_refund_obtained argument */ 1626 { 1627 const char *await_refund_obtained_s; 1628 1629 await_refund_obtained_s = 1630 MHD_lookup_connection_value (connection, 1631 MHD_GET_ARGUMENT_KIND, 1632 "await_refund_obtained"); 1633 god->sc.awaiting_refund_obtained = 1634 (NULL != await_refund_obtained_s) 1635 ? 0 == strcasecmp (await_refund_obtained_s, 1636 "yes") 1637 : false; 1638 if (god->sc.awaiting_refund_obtained) 1639 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1640 "Awaiting refund obtained\n"); 1641 } 1642 1643 TALER_MHD_parse_request_amount (connection, 1644 "refund", 1645 &god->sc.refund_expected); 1646 if (TALER_amount_is_valid (&god->sc.refund_expected)) 1647 { 1648 god->sc.awaiting_refund = true; 1649 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1650 "Awaiting minimum refund of %s\n", 1651 TALER_amount2s (&god->sc.refund_expected)); 1652 } 1653 TALER_MHD_parse_request_timeout (connection, 1654 &god->sc.long_poll_timeout); 1655 } 1656 1657 if (GNUNET_SYSERR == god->suspended) 1658 return MHD_NO; /* we are in shutdown */ 1659 if (GNUNET_YES == god->suspended) 1660 { 1661 god->suspended = GNUNET_NO; 1662 GNUNET_CONTAINER_DLL_remove (god_head, 1663 god_tail, 1664 god); 1665 } 1666 1667 while (1) 1668 { 1669 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1670 "Handling request in phase %d\n", 1671 (int) god->phase); 1672 switch (god->phase) 1673 { 1674 case GOP_INIT: 1675 phase_init (god); 1676 break; 1677 case GOP_LOOKUP_TERMS: 1678 phase_lookup_terms (god); 1679 break; 1680 case GOP_PARSE_CONTRACT: 1681 phase_parse_contract (god); 1682 break; 1683 case GOP_CHECK_CLIENT_ACCESS: 1684 phase_check_client_access (god); 1685 break; 1686 case GOP_CHECK_PAID: 1687 phase_check_paid (god); 1688 break; 1689 case GOP_REDIRECT_TO_PAID_ORDER: 1690 if (phase_redirect_to_paid_order (god)) 1691 return MHD_YES; 1692 break; 1693 case GOP_HANDLE_UNPAID: 1694 if (phase_handle_unpaid (god)) 1695 return MHD_YES; 1696 break; 1697 case GOP_CHECK_REFUNDED: 1698 if (phase_check_refunded (god)) 1699 return MHD_YES; 1700 break; 1701 case GOP_RETURN_STATUS: 1702 phase_return_status (god); 1703 break; 1704 case GOP_RETURN_MHD_YES: 1705 return MHD_YES; 1706 case GOP_RETURN_MHD_NO: 1707 return MHD_NO; 1708 } 1709 } 1710 } 1711 1712 1713 /* end of taler-merchant-httpd_get-orders-ID.c */