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