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