taler-merchant-httpd_get-orders-ORDER_ID.c (59185B)
1 /* 2 This file is part of TALER 3 (C) 2014-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU 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/get_contract_terms_status.h" 39 #include "merchant-database/get_order.h" 40 #include "merchant-database/get_order_by_fulfillment.h" 41 #include "merchant-database/get_order_status.h" 42 #include "merchant-database/iterate_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 /* Return the current status after a relevant event. In particular, a 551 claim changes an order from unclaimed to claimed without changing its 552 unpaid HTTP response, so re-entering the long poll here would hide the 553 state transition until the original timeout. */ 554 god->sc.long_poll_timeout = GNUNET_TIME_absolute_get (); 555 god->suspended = GNUNET_NO; 556 GNUNET_CONTAINER_DLL_remove (god_head, 557 god_tail, 558 god); 559 MHD_resume_connection (god->sc.con); 560 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 561 GNUNET_async_scope_restore (&old); 562 } 563 564 565 /** 566 * First phase (after request parsing). 567 * Set up long-polling. 568 * 569 * @param[in,out] god request context 570 */ 571 static void 572 phase_init (struct GetOrderData *god) 573 { 574 god->phase++; 575 if (god->generate_html) 576 return; /* If HTML is requested, we never actually long poll. */ 577 if (! GNUNET_TIME_absolute_is_future (god->sc.long_poll_timeout)) 578 return; /* long polling not requested */ 579 580 if (god->sc.awaiting_refund || 581 god->sc.awaiting_refund_obtained) 582 { 583 struct TMH_OrderPayEventP refund_eh = { 584 .header.size = htons (sizeof (refund_eh)), 585 .header.type = htons (god->sc.awaiting_refund_obtained 586 ? TALER_DBEVENT_MERCHANT_REFUND_OBTAINED 587 : TALER_DBEVENT_MERCHANT_ORDER_REFUND), 588 .merchant_pub = god->hc->instance->merchant_pub 589 }; 590 591 GNUNET_CRYPTO_hash (god->order_id, 592 strlen (god->order_id), 593 &refund_eh.h_order_id); 594 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 595 "Subscribing %p to refunds on %s\n", 596 god, 597 god->order_id); 598 god->refund_eh 599 = TALER_MERCHANTDB_event_listen ( 600 TMH_db, 601 &refund_eh.header, 602 GNUNET_TIME_absolute_get_remaining ( 603 god->sc.long_poll_timeout), 604 &resume_by_event, 605 god); 606 } 607 { 608 struct TMH_OrderPayEventP pay_eh = { 609 .header.size = htons (sizeof (pay_eh)), 610 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED), 611 .merchant_pub = god->hc->instance->merchant_pub 612 }; 613 614 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 615 "Subscribing to order status changes on %s\n", 616 god->order_id); 617 GNUNET_CRYPTO_hash (god->order_id, 618 strlen (god->order_id), 619 &pay_eh.h_order_id); 620 god->pay_eh 621 = TALER_MERCHANTDB_event_listen ( 622 TMH_db, 623 &pay_eh.header, 624 GNUNET_TIME_absolute_get_remaining ( 625 god->sc.long_poll_timeout), 626 &resume_by_event, 627 god); 628 } 629 } 630 631 632 /** 633 * Lookup contract terms and check client has the 634 * right to access this order (by claim token or 635 * contract hash). 636 * 637 * @param[in,out] god request context 638 */ 639 static void 640 phase_lookup_terms (struct GetOrderData *god) 641 { 642 uint64_t order_serial; 643 struct TALER_ClaimTokenP db_claim_token; 644 645 /* Convert order_id to h_contract_terms */ 646 TALER_MERCHANTDB_preflight (TMH_db); 647 GNUNET_assert (NULL == god->contract_terms_json); 648 GNUNET_assert (NULL == god->order_json); 649 650 { 651 enum GNUNET_DB_QueryStatus qs; 652 653 bool paid; 654 bool wired; 655 bool session_matches; 656 qs = TALER_MERCHANTDB_get_contract_terms_status ( 657 TMH_db, 658 god->hc->instance->settings.id, 659 god->order_id, 660 NULL, 661 &god->contract_terms_json, 662 &order_serial, 663 &paid, 664 &wired, 665 &session_matches, 666 &db_claim_token, 667 &god->choice_index); 668 if (0 > qs) 669 { 670 /* single, read-only SQL statements should never cause 671 serialization problems */ 672 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 673 /* Always report on hard error as well to enable diagnostics */ 674 GNUNET_break (0); 675 phase_fail (god, 676 MHD_HTTP_INTERNAL_SERVER_ERROR, 677 TALER_EC_GENERIC_DB_FETCH_FAILED, 678 "get_contract_terms"); 679 return; 680 } 681 /* Note: when "!ord.requireClaimToken" and the client does not provide 682 a claim token (all zeros!), then token_match==TRUE below: */ 683 god->token_match 684 = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 685 && (0 == GNUNET_memcmp (&db_claim_token, 686 &god->claim_token)); 687 } 688 689 /* Check if client provided the right hash code of the contract terms */ 690 if (NULL != god->contract_terms_json) 691 { 692 god->contract_available = true; 693 if (GNUNET_YES == 694 GNUNET_is_zero (&god->h_contract_terms)) 695 { 696 if (GNUNET_OK != 697 TALER_JSON_contract_hash (god->contract_terms_json, 698 &god->h_contract_terms)) 699 { 700 GNUNET_break (0); 701 phase_fail (god, 702 MHD_HTTP_INTERNAL_SERVER_ERROR, 703 TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, 704 "contract terms"); 705 return; 706 } 707 } 708 else 709 { 710 struct TALER_PrivateContractHashP h; 711 712 if (GNUNET_OK != 713 TALER_JSON_contract_hash (god->contract_terms_json, 714 &h)) 715 { 716 GNUNET_break (0); 717 phase_fail (god, 718 MHD_HTTP_INTERNAL_SERVER_ERROR, 719 TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, 720 "contract terms"); 721 return; 722 } 723 god->contract_match = (0 == 724 GNUNET_memcmp (&h, 725 &god->h_contract_terms)); 726 if (! god->contract_match) 727 { 728 GNUNET_break_op (0); 729 phase_fail (god, 730 MHD_HTTP_FORBIDDEN, 731 TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER, 732 NULL); 733 return; 734 } 735 } 736 } 737 738 if (god->contract_available) 739 { 740 god->claimed = true; 741 } 742 else 743 { 744 struct TALER_MerchantPostDataHashP unused; 745 enum GNUNET_DB_QueryStatus qs; 746 747 /* No contract exists yet, so no payment choice has been selected. */ 748 god->choice_index = -1; 749 750 qs = TALER_MERCHANTDB_get_order ( 751 TMH_db, 752 god->hc->instance->settings.id, 753 god->order_id, 754 &db_claim_token, 755 &unused, 756 &god->order_json); 757 if (0 > qs) 758 { 759 /* single, read-only SQL statements should never cause 760 serialization problems */ 761 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 762 /* Always report on hard error as well to enable diagnostics */ 763 GNUNET_break (0); 764 phase_fail (god, 765 MHD_HTTP_INTERNAL_SERVER_ERROR, 766 TALER_EC_GENERIC_DB_FETCH_FAILED, 767 "get_order"); 768 return; 769 } 770 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 771 { 772 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 773 "Unknown order id given: `%s'\n", 774 god->order_id); 775 phase_fail (god, 776 MHD_HTTP_NOT_FOUND, 777 TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN, 778 god->order_id); 779 return; 780 } 781 /* Note: when "!ord.requireClaimToken" and the client does not provide 782 a claim token (all zeros!), then token_match==TRUE below: */ 783 god->token_match 784 = (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && 785 (0 == GNUNET_memcmp (&db_claim_token, 786 &god->claim_token)); 787 } /* end unclaimed order logic */ 788 god->phase++; 789 } 790 791 792 /** 793 * Parse contract terms. 794 * 795 * @param[in,out] god request context 796 */ 797 static void 798 phase_parse_contract (struct GetOrderData *god) 799 { 800 GNUNET_break (NULL == god->contract_terms); 801 GNUNET_break (NULL == god->order); 802 if (NULL != god->contract_terms_json) 803 { 804 if (NULL == 805 json_object_get (god->contract_terms_json, 806 "nonce")) 807 { 808 god->pc = TALER_MERCHANT_proto_contract_parse ( 809 god->contract_terms_json); 810 if (NULL == god->pc) 811 { 812 phase_fail (god, 813 MHD_HTTP_INTERNAL_SERVER_ERROR, 814 TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID, 815 god->order_id); 816 return; 817 } 818 god->ct = god->pc->base; 819 } 820 else 821 { 822 god->contract_terms = TALER_MERCHANT_contract_parse ( 823 god->contract_terms_json); 824 if (NULL == god->contract_terms) 825 { 826 phase_fail (god, 827 MHD_HTTP_INTERNAL_SERVER_ERROR, 828 TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID, 829 god->order_id); 830 return; 831 } 832 god->pc = god->contract_terms->pc; 833 god->ct = god->contract_terms->pc->base; 834 } 835 } 836 if (NULL != god->order_json) 837 { 838 god->order = TALER_MERCHANT_order_parse ( 839 god->order_json); 840 if (NULL == god->order) 841 { 842 phase_fail (god, 843 MHD_HTTP_INTERNAL_SERVER_ERROR, 844 TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID, 845 god->order_id); 846 return; 847 } 848 god->order_parsed = true; 849 god->ct = god->order->base; 850 } 851 GNUNET_assert ( (NULL != god->order) || 852 (NULL != god->pc) ); 853 854 if ( (NULL != god->session_id) && 855 (NULL != god->ct->fulfillment_url) && 856 (NULL == god->session_eh) ) 857 { 858 struct TMH_SessionEventP session_eh = { 859 .header.size = htons (sizeof (session_eh)), 860 .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED), 861 .merchant_pub = god->hc->instance->merchant_pub 862 }; 863 864 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 865 "Subscribing to session triggers for %p\n", 866 god); 867 GNUNET_CRYPTO_hash (god->session_id, 868 strlen (god->session_id), 869 &session_eh.h_session_id); 870 GNUNET_CRYPTO_hash (god->ct->fulfillment_url, 871 strlen (god->ct->fulfillment_url), 872 &session_eh.h_fulfillment_url); 873 god->session_eh 874 = TALER_MERCHANTDB_event_listen ( 875 TMH_db, 876 &session_eh.header, 877 GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout), 878 &resume_by_event, 879 god); 880 } 881 god->phase++; 882 } 883 884 885 /** 886 * Check that this order is unclaimed or claimed by 887 * this client. 888 * 889 * @param[in,out] god request context 890 */ 891 static void 892 phase_check_client_access (struct GetOrderData *god) 893 { 894 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 895 "Token match: %d, contract_available: %d, contract match: %d, claimed: %d\n", 896 god->token_match, 897 god->contract_available, 898 god->contract_match, 899 god->claimed); 900 901 if (god->claim_token_provided && ! god->token_match) 902 { 903 /* Authentication provided but wrong. */ 904 GNUNET_break_op (0); 905 phase_fail (god, 906 MHD_HTTP_FORBIDDEN, 907 TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN, 908 "authentication with claim token provided but wrong"); 909 return; 910 } 911 912 if (god->h_contract_provided && ! god->contract_match) 913 { 914 /* Authentication provided but wrong. */ 915 GNUNET_break_op (0); 916 phase_fail (god, 917 MHD_HTTP_FORBIDDEN, 918 TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_HASH, 919 NULL); 920 return; 921 } 922 923 if (! (god->token_match || 924 god->contract_match) ) 925 { 926 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 927 "Neither claim token nor contract matched\n"); 928 /* Client has no rights to this order */ 929 if (NULL == god->ct->public_reorder_url) 930 { 931 /* We cannot give the client a new order, just fail */ 932 if (! GNUNET_is_zero (&god->h_contract_terms)) 933 { 934 GNUNET_break_op (0); 935 phase_fail (god, 936 MHD_HTTP_FORBIDDEN, 937 TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER, 938 NULL); 939 return; 940 } 941 GNUNET_break_op (0); 942 phase_fail (god, 943 MHD_HTTP_FORBIDDEN, 944 TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_TOKEN, 945 "no 'public_reorder_url'"); 946 return; 947 } 948 /* We have a fulfillment URL, redirect the client there, maybe 949 the frontend can generate a fresh order for this new customer */ 950 if (god->generate_html) 951 { 952 /* Contract was claimed (maybe by another device), so this client 953 cannot get the status information. Redirect to fulfillment page, 954 where the client may be able to pickup a fresh order -- or might 955 be able authenticate via session ID */ 956 struct MHD_Response *reply; 957 enum MHD_Result ret; 958 959 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 960 "Contract claimed, redirecting to fulfillment page for order %s\n", 961 god->order_id); 962 reply = MHD_create_response_from_buffer (0, 963 NULL, 964 MHD_RESPMEM_PERSISTENT); 965 if (NULL == reply) 966 { 967 GNUNET_break (0); 968 phase_end (god, 969 MHD_NO); 970 return; 971 } 972 GNUNET_break (MHD_YES == 973 MHD_add_response_header ( 974 reply, 975 MHD_HTTP_HEADER_LOCATION, 976 god->ct->public_reorder_url)); 977 ret = MHD_queue_response (god->sc.con, 978 MHD_HTTP_FOUND, 979 reply); 980 MHD_destroy_response (reply); 981 phase_end (god, 982 ret); 983 return; 984 } 985 /* Need to generate JSON reply */ 986 phase_end (god, 987 TALER_MHD_REPLY_JSON_PACK ( 988 god->sc.con, 989 MHD_HTTP_ACCEPTED, 990 GNUNET_JSON_pack_string ( 991 "public_reorder_url", 992 god->ct->public_reorder_url))); 993 return; 994 } 995 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 996 "Claim token or contract matched\n"); 997 god->phase++; 998 } 999 1000 1001 /** 1002 * Return the order summary of the contract of @a god in the 1003 * preferred language of the HTTP client. 1004 * 1005 * @param god order to extract summary from 1006 * @return dummy error message summary if no summary was provided in the contract 1007 */ 1008 static const char * 1009 get_order_summary (const struct GetOrderData *god) 1010 { 1011 const char *language_pattern; 1012 const char *ret; 1013 json_t *terms; 1014 1015 if (NULL != god->contract_terms_json) 1016 terms = god->contract_terms_json; 1017 else 1018 terms = god->order_json; 1019 language_pattern = MHD_lookup_connection_value ( 1020 god->sc.con, 1021 MHD_HEADER_KIND, 1022 MHD_HTTP_HEADER_ACCEPT_LANGUAGE); 1023 if (NULL == language_pattern) 1024 language_pattern = "en"; 1025 ret = json_string_value (TALER_JSON_extract_i18n ( 1026 terms, 1027 language_pattern, 1028 "summary")); 1029 if (NULL == ret) 1030 { 1031 /* Upon order creation (and insertion into the database), the presence 1032 of a summary should have been checked. So if we get here, someone 1033 did something fishy to our database... */ 1034 GNUNET_break (0); 1035 ret = "<bug: no summary>"; 1036 } 1037 return ret; 1038 } 1039 1040 1041 /** 1042 * Return the localized fulfillment message of the contract of @a god. 1043 * 1044 * @param god order to extract the fulfillment message from 1045 * @return localized message, or NULL if no message was provided 1046 */ 1047 static const char * 1048 get_fulfillment_message (const struct GetOrderData *god) 1049 { 1050 const char *language_pattern; 1051 const char *ret; 1052 json_t *terms; 1053 1054 if (NULL != god->contract_terms_json) 1055 terms = god->contract_terms_json; 1056 else 1057 terms = god->order_json; 1058 language_pattern = MHD_lookup_connection_value ( 1059 god->sc.con, 1060 MHD_HEADER_KIND, 1061 MHD_HTTP_HEADER_ACCEPT_LANGUAGE); 1062 if (NULL == language_pattern) 1063 language_pattern = "en"; 1064 ret = json_string_value (TALER_JSON_extract_i18n ( 1065 terms, 1066 language_pattern, 1067 "fulfillment_message")); 1068 if (NULL == ret) 1069 ret = god->ct->fulfillment_message; 1070 return ret; 1071 } 1072 1073 1074 /** 1075 * Return the amount to display for the order. Before a version 1 order with 1076 * multiple choices is claimed, the exact amount is selected by the wallet. 1077 * 1078 * @param god order to extract the amount from 1079 * @param[out] multiple_payment_options set if the wallet selects the amount 1080 * @return order amount, or NULL if no single amount is available 1081 */ 1082 static const struct TALER_Amount * 1083 get_order_amount (const struct GetOrderData *god, 1084 bool *multiple_payment_options) 1085 { 1086 *multiple_payment_options = false; 1087 switch (god->ct->version) 1088 { 1089 case TALER_MERCHANT_CONTRACT_VERSION_0: 1090 return (NULL != god->pc) 1091 ? &god->pc->details.v0.brutto 1092 : &god->order->details.v0.brutto; 1093 case TALER_MERCHANT_CONTRACT_VERSION_1: 1094 { 1095 unsigned int choices_len = (NULL != god->pc) 1096 ? god->pc->details.v1.choices_len 1097 : god->order->details.v1.choices_len; 1098 1099 if (god->choice_index >= 0) 1100 { 1101 if (god->choice_index >= choices_len) 1102 { 1103 GNUNET_break (0); 1104 return NULL; 1105 } 1106 return (NULL != god->pc) 1107 ? &god->pc->details.v1.choices[god->choice_index].amount 1108 : &god->order->details.v1.choices[god->choice_index].amount; 1109 } 1110 if (1 == choices_len) 1111 return (NULL != god->pc) 1112 ? &god->pc->details.v1.choices[0].amount 1113 : &god->order->details.v1.choices[0].amount; 1114 *multiple_payment_options = true; 1115 return NULL; 1116 } 1117 default: 1118 GNUNET_break (0); 1119 return NULL; 1120 } 1121 } 1122 1123 1124 /** 1125 * The client did not yet pay, send it the payment request. 1126 * 1127 * @param god check pay request context 1128 * @param already_paid_order_id if for the fulfillment URI there is 1129 * already a paid order, this is the order ID to redirect 1130 * the wallet to; NULL if not applicable 1131 * @return true to exit due to suspension 1132 */ 1133 static bool 1134 send_pay_request (struct GetOrderData *god, 1135 const char *already_paid_order_id) 1136 { 1137 enum MHD_Result ret; 1138 char *taler_pay_uri; 1139 char *order_status_url; 1140 struct GNUNET_TIME_Relative remaining; 1141 1142 remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout); 1143 if ( (! GNUNET_TIME_relative_is_zero (remaining)) && 1144 (! god->generate_html) && 1145 (NULL == already_paid_order_id) ) 1146 { 1147 /* long polling: do not queue a response, suspend connection instead */ 1148 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1149 "Suspending request: long polling for payment\n"); 1150 suspend_god (god); 1151 return true; 1152 } 1153 /* Check if resource_id has been paid for in the same session 1154 * with another order_id. 1155 */ 1156 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1157 "Sending payment request\n"); 1158 taler_pay_uri = TMH_make_taler_pay_uri ( 1159 god->sc.con, 1160 god->order_id, 1161 god->session_id, 1162 god->hc->instance->settings.id, 1163 &god->claim_token); 1164 order_status_url = TMH_make_order_status_url ( 1165 god->sc.con, 1166 god->order_id, 1167 god->session_id, 1168 god->hc->instance->settings.id, 1169 &god->claim_token, 1170 NULL); 1171 if ( (NULL == taler_pay_uri) || 1172 (NULL == order_status_url) ) 1173 { 1174 GNUNET_break_op (0); 1175 GNUNET_free (taler_pay_uri); 1176 GNUNET_free (order_status_url); 1177 phase_fail (god, 1178 MHD_HTTP_BAD_REQUEST, 1179 TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED, 1180 "host"); 1181 return false; 1182 } 1183 if (god->generate_html) 1184 { 1185 if (NULL != already_paid_order_id) 1186 { 1187 struct MHD_Response *reply; 1188 1189 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1190 "Redirecting to already paid order %s via fulfillment URL %s\n", 1191 already_paid_order_id, 1192 god->ct->fulfillment_url); 1193 reply = MHD_create_response_from_buffer (0, 1194 NULL, 1195 MHD_RESPMEM_PERSISTENT); 1196 if (NULL == reply) 1197 { 1198 GNUNET_break (0); 1199 phase_end (god, 1200 MHD_NO); 1201 return false; 1202 } 1203 GNUNET_break (MHD_YES == 1204 MHD_add_response_header ( 1205 reply, 1206 MHD_HTTP_HEADER_LOCATION, 1207 god->ct->fulfillment_url)); 1208 { 1209 ret = MHD_queue_response (god->sc.con, 1210 MHD_HTTP_FOUND, 1211 reply); 1212 MHD_destroy_response (reply); 1213 phase_end (god, 1214 ret); 1215 return false; 1216 } 1217 } 1218 1219 { 1220 char *qr; 1221 bool multiple_payment_options; 1222 const struct TALER_Amount *order_amount; 1223 struct GNUNET_TIME_Timestamp pay_deadline; 1224 1225 order_amount = get_order_amount (god, 1226 &multiple_payment_options); 1227 pay_deadline = (NULL != god->pc) 1228 ? god->pc->pay_deadline 1229 : god->order->pay_deadline; 1230 1231 qr = TMH_create_qrcode (taler_pay_uri); 1232 if (NULL == qr) 1233 { 1234 GNUNET_break (0); 1235 phase_end (god, 1236 MHD_NO); 1237 return false; 1238 } 1239 { 1240 enum GNUNET_GenericReturnValue res; 1241 json_t *context; 1242 1243 context = GNUNET_JSON_PACK ( 1244 GNUNET_JSON_pack_string ("taler_pay_uri", 1245 taler_pay_uri), 1246 GNUNET_JSON_pack_string ("order_status_url", 1247 order_status_url), 1248 GNUNET_JSON_pack_string ("taler_pay_qrcode_svg", 1249 qr), 1250 GNUNET_JSON_pack_string ("order_summary", 1251 get_order_summary (god)), 1252 GNUNET_JSON_pack_string ("order_id", 1253 god->order_id), 1254 GNUNET_JSON_pack_string ("merchant_name", 1255 god->hc->instance->settings.name), 1256 GNUNET_JSON_pack_allow_null ( 1257 TALER_JSON_pack_amount ("order_amount", 1258 order_amount)), 1259 GNUNET_JSON_pack_bool ("multiple_payment_options", 1260 multiple_payment_options), 1261 GNUNET_JSON_pack_bool ("claimed", 1262 god->claimed), 1263 GNUNET_JSON_pack_timestamp ("pay_deadline", 1264 pay_deadline)); 1265 res = TALER_TEMPLATING_reply ( 1266 god->sc.con, 1267 MHD_HTTP_PAYMENT_REQUIRED, 1268 "request_payment", 1269 god->hc->instance->settings.id, 1270 taler_pay_uri, 1271 context); 1272 if (GNUNET_SYSERR == res) 1273 { 1274 GNUNET_break (0); 1275 ret = MHD_NO; 1276 } 1277 else 1278 { 1279 ret = MHD_YES; 1280 } 1281 json_decref (context); 1282 } 1283 GNUNET_free (qr); 1284 } 1285 } 1286 else /* end of 'generate HTML' */ 1287 { 1288 ret = TALER_MHD_REPLY_JSON_PACK ( 1289 god->sc.con, 1290 MHD_HTTP_PAYMENT_REQUIRED, 1291 GNUNET_JSON_pack_string ("taler_pay_uri", 1292 taler_pay_uri), 1293 GNUNET_JSON_pack_allow_null ( 1294 GNUNET_JSON_pack_string ("fulfillment_url", 1295 god->ct->fulfillment_url)), 1296 GNUNET_JSON_pack_allow_null ( 1297 GNUNET_JSON_pack_string ("already_paid_order_id", 1298 already_paid_order_id))); 1299 } 1300 GNUNET_free (taler_pay_uri); 1301 GNUNET_free (order_status_url); 1302 phase_end (god, 1303 ret); 1304 return false; 1305 } 1306 1307 1308 /** 1309 * Check if the order has been paid. 1310 * 1311 * @param[in,out] god request context 1312 */ 1313 static void 1314 phase_check_paid (struct GetOrderData *god) 1315 { 1316 enum GNUNET_DB_QueryStatus qs; 1317 struct TALER_PrivateContractHashP h_contract; 1318 1319 god->paid = false; 1320 qs = TALER_MERCHANTDB_get_order_status ( 1321 TMH_db, 1322 god->hc->instance->settings.id, 1323 god->order_id, 1324 &h_contract, 1325 &god->paid); 1326 if (0 > qs) 1327 { 1328 /* Always report on hard error as well to enable diagnostics */ 1329 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 1330 phase_fail (god, 1331 MHD_HTTP_INTERNAL_SERVER_ERROR, 1332 TALER_EC_GENERIC_DB_FETCH_FAILED, 1333 "get_order_status"); 1334 return; 1335 } 1336 god->phase++; 1337 } 1338 1339 1340 /** 1341 * Check if the client already paid for an equivalent 1342 * order under this session, and if so redirect to 1343 * that order. 1344 * 1345 * @param[in,out] god request context 1346 * @return true to exit due to suspension 1347 */ 1348 static bool 1349 phase_redirect_to_paid_order (struct GetOrderData *god) 1350 { 1351 if ( (NULL != god->session_id) && 1352 (NULL != god->ct->fulfillment_url) ) 1353 { 1354 /* Check if client paid for this fulfillment article 1355 already within this session, but using a different 1356 order ID. If so, redirect the client to the order 1357 it already paid. Allows, for example, the case 1358 where a mobile phone pays for a browser's session, 1359 where the mobile phone has a different order 1360 ID (because it purchased the article earlier) 1361 than the one that the browser is waiting for. */ 1362 char *already_paid_order_id = NULL; 1363 enum GNUNET_DB_QueryStatus qs; 1364 1365 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1366 "Running re-purchase detection for %s/%s\n", 1367 god->session_id, 1368 god->ct->fulfillment_url); 1369 qs = TALER_MERCHANTDB_get_order_by_fulfillment ( 1370 TMH_db, 1371 god->hc->instance->settings.id, 1372 god->ct->fulfillment_url, 1373 god->session_id, 1374 TALER_EXCHANGE_YNA_NO != god->allow_refunded_for_repurchase, 1375 &already_paid_order_id); 1376 if (qs < 0) 1377 { 1378 /* single, read-only SQL statements should never cause 1379 serialization problems */ 1380 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 1381 /* Always report on hard error as well to enable diagnostics */ 1382 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 1383 phase_fail (god, 1384 MHD_HTTP_INTERNAL_SERVER_ERROR, 1385 TALER_EC_GENERIC_DB_FETCH_FAILED, 1386 "order by fulfillment"); 1387 return false; 1388 } 1389 if ( (! god->paid) && 1390 ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || 1391 (0 != strcmp (god->order_id, 1392 already_paid_order_id)) ) ) 1393 { 1394 bool ret; 1395 1396 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1397 "Sending pay request for order %s (already paid: %s)\n", 1398 god->order_id, 1399 already_paid_order_id); 1400 ret = send_pay_request (god, 1401 already_paid_order_id); 1402 GNUNET_free (already_paid_order_id); 1403 return ret; 1404 } 1405 GNUNET_free (already_paid_order_id); 1406 } 1407 god->phase++; 1408 return false; 1409 } 1410 1411 1412 /** 1413 * Check if the order has been paid, and if not 1414 * request payment. 1415 * 1416 * @param[in,out] god request context 1417 * @return true to exit due to suspension 1418 */ 1419 static bool 1420 phase_handle_unpaid (struct GetOrderData *god) 1421 { 1422 if (god->paid) 1423 { 1424 god->phase++; 1425 return false; 1426 } 1427 if (god->claimed) 1428 { 1429 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1430 "Order claimed but unpaid, sending pay request for order %s\n", 1431 god->order_id); 1432 } 1433 else 1434 { 1435 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1436 "Order unclaimed, sending pay request for order %s\n", 1437 god->order_id); 1438 } 1439 return send_pay_request (god, 1440 NULL); 1441 } 1442 1443 1444 /** 1445 * Function called with detailed information about a refund. 1446 * It is responsible for packing up the data to return. 1447 * 1448 * @param cls closure 1449 * @param refund_serial unique serial number of the refund 1450 * @param timestamp time of the refund (for grouping of refunds in the wallet UI) 1451 * @param coin_pub public coin from which the refund comes from 1452 * @param exchange_url URL of the exchange that issued @a coin_pub 1453 * @param rtransaction_id identificator of the refund 1454 * @param reason human-readable explanation of the refund 1455 * @param refund_amount refund amount which is being taken from @a coin_pub 1456 * @param pending true if the this refund was not yet processed by the wallet/exchange 1457 */ 1458 static void 1459 process_refunds_cb (void *cls, 1460 uint64_t refund_serial, 1461 struct GNUNET_TIME_Timestamp timestamp, 1462 const struct TALER_CoinSpendPublicKeyP *coin_pub, 1463 const char *exchange_url, 1464 uint64_t rtransaction_id, 1465 const char *reason, 1466 const struct TALER_Amount *refund_amount, 1467 bool pending) 1468 { 1469 struct GetOrderData *god = cls; 1470 1471 (void) refund_serial; 1472 (void) timestamp; 1473 (void) exchange_url; 1474 (void) rtransaction_id; 1475 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1476 "Found refund of %s for coin %s with reason `%s' in database\n", 1477 TALER_amount2s (refund_amount), 1478 TALER_B2S (coin_pub), 1479 reason); 1480 god->refund_pending |= pending; 1481 if ( (GNUNET_OK != 1482 TALER_amount_cmp_currency (&god->refund_taken, 1483 refund_amount)) || 1484 (GNUNET_OK != 1485 TALER_amount_cmp_currency (&god->refund_amount, 1486 refund_amount)) ) 1487 { 1488 god->bad_refund_currency_in_db = true; 1489 return; 1490 } 1491 if (! pending) 1492 { 1493 GNUNET_assert (0 <= 1494 TALER_amount_add (&god->refund_taken, 1495 &god->refund_taken, 1496 refund_amount)); 1497 } 1498 GNUNET_assert (0 <= 1499 TALER_amount_add (&god->refund_amount, 1500 &god->refund_amount, 1501 refund_amount)); 1502 god->refunded = true; 1503 } 1504 1505 1506 /** 1507 * Check if the order has been refunded. 1508 * 1509 * @param[in,out] god request context 1510 * @return true to exit due to suspension 1511 */ 1512 static bool 1513 phase_check_refunded (struct GetOrderData *god) 1514 { 1515 enum GNUNET_DB_QueryStatus qs; 1516 struct TALER_Amount refund_amount; 1517 const char *refund_currency; 1518 1519 GNUNET_assert (NULL != god->contract_terms); 1520 switch (god->ct->version) 1521 { 1522 case TALER_MERCHANT_CONTRACT_VERSION_0: 1523 refund_amount = god->pc->details.v0.brutto; 1524 refund_currency = god->pc->details.v0.brutto.currency; 1525 break; 1526 case TALER_MERCHANT_CONTRACT_VERSION_1: 1527 if (god->choice_index < 0) 1528 { 1529 // order was not paid, no refund to be checked 1530 god->phase++; 1531 return false; 1532 } 1533 GNUNET_assert (god->choice_index < 1534 god->pc->details.v1.choices_len); 1535 refund_currency = god->pc->details.v1.choices[ 1536 god->choice_index].amount.currency; 1537 GNUNET_assert (GNUNET_OK == 1538 TALER_amount_set_zero (refund_currency, 1539 &refund_amount)); 1540 break; 1541 default: 1542 { 1543 GNUNET_break (0); 1544 phase_fail (god, 1545 MHD_HTTP_INTERNAL_SERVER_ERROR, 1546 TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION, 1547 NULL); 1548 return false; 1549 } 1550 } 1551 1552 if ( (god->sc.awaiting_refund) && 1553 (GNUNET_OK != 1554 TALER_amount_cmp_currency (&refund_amount, 1555 &god->sc.refund_expected)) ) 1556 { 1557 GNUNET_break (0); 1558 phase_fail (god, 1559 MHD_HTTP_CONFLICT, 1560 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1561 refund_currency); 1562 return false; 1563 } 1564 1565 /* At this point, we know the contract was paid. Let's check for 1566 refunds. First, clear away refunds found from previous invocations. */ 1567 GNUNET_assert (GNUNET_OK == 1568 TALER_amount_set_zero (refund_currency, 1569 &god->refund_amount)); 1570 GNUNET_assert (GNUNET_OK == 1571 TALER_amount_set_zero (refund_currency, 1572 &god->refund_taken)); 1573 qs = TALER_MERCHANTDB_iterate_refunds_detailed ( 1574 TMH_db, 1575 god->hc->instance->settings.id, 1576 &god->h_contract_terms, 1577 &process_refunds_cb, 1578 god); 1579 if (0 > qs) 1580 { 1581 GNUNET_break (0); 1582 phase_fail (god, 1583 MHD_HTTP_INTERNAL_SERVER_ERROR, 1584 TALER_EC_GENERIC_DB_FETCH_FAILED, 1585 "iterate_refunds_detailed"); 1586 return false; 1587 } 1588 if (god->bad_refund_currency_in_db) 1589 { 1590 GNUNET_break (0); 1591 phase_fail (god, 1592 MHD_HTTP_INTERNAL_SERVER_ERROR, 1593 TALER_EC_GENERIC_DB_FETCH_FAILED, 1594 "currency mix-up between contract price and refunds in database"); 1595 return false; 1596 } 1597 if ( ((god->sc.awaiting_refund) && 1598 ( (! god->refunded) || 1599 (1 != TALER_amount_cmp (&god->refund_amount, 1600 &god->sc.refund_expected)) )) || 1601 ( (god->sc.awaiting_refund_obtained) && 1602 (god->refund_pending) ) ) 1603 { 1604 /* Client is waiting for a refund larger than what we have, suspend 1605 until timeout */ 1606 struct GNUNET_TIME_Relative remaining; 1607 1608 remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout); 1609 if ( (! GNUNET_TIME_relative_is_zero (remaining)) && 1610 (! god->generate_html) ) 1611 { 1612 /* yes, indeed suspend */ 1613 if (god->sc.awaiting_refund) 1614 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1615 "Awaiting refund exceeding %s\n", 1616 TALER_amount2s (&god->sc.refund_expected)); 1617 if (god->sc.awaiting_refund_obtained) 1618 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1619 "Awaiting pending refunds\n"); 1620 suspend_god (god); 1621 return true; 1622 } 1623 } 1624 god->phase++; 1625 return false; 1626 } 1627 1628 1629 /** 1630 * Create a taler://refund/ URI for the given @a con and @a order_id 1631 * and @a instance_id. 1632 * 1633 * @param merchant_base_url URL to take host and path from; 1634 * we cannot take it from the MHD connection as a browser 1635 * may have changed 'http' to 'https' and we MUST be consistent 1636 * with what the merchant's frontend used initially 1637 * @param order_id the order id 1638 * @return corresponding taler://refund/ URI, or NULL on missing "host" 1639 */ 1640 static char * 1641 make_taler_refund_uri (const char *merchant_base_url, 1642 const char *order_id) 1643 { 1644 struct GNUNET_Buffer buf = { 0 }; 1645 char *url; 1646 struct GNUNET_Uri uri; 1647 1648 url = GNUNET_strdup (merchant_base_url); 1649 if (-1 == GNUNET_uri_parse (&uri, 1650 url)) 1651 { 1652 GNUNET_break (0); 1653 GNUNET_free (url); 1654 return NULL; 1655 } 1656 GNUNET_assert (NULL != order_id); 1657 GNUNET_buffer_write_str (&buf, 1658 "taler"); 1659 if (0 == strcasecmp ("http", 1660 uri.scheme)) 1661 GNUNET_buffer_write_str (&buf, 1662 "+http"); 1663 GNUNET_buffer_write_str (&buf, 1664 "://refund/"); 1665 GNUNET_buffer_write_str (&buf, 1666 uri.host); 1667 if (0 != uri.port) 1668 GNUNET_buffer_write_fstr (&buf, 1669 ":%u", 1670 (unsigned int) uri.port); 1671 if (NULL != uri.path) 1672 GNUNET_buffer_write_path (&buf, 1673 uri.path); 1674 GNUNET_buffer_write_path (&buf, 1675 order_id); 1676 GNUNET_buffer_write_path (&buf, 1677 ""); // Trailing slash 1678 GNUNET_free (url); 1679 return GNUNET_buffer_reap_str (&buf); 1680 } 1681 1682 1683 /** 1684 * Generate the order status response. 1685 * 1686 * @param[in,out] god request context 1687 */ 1688 static void 1689 phase_return_status (struct GetOrderData *god) 1690 { 1691 /* All operations done, build final response */ 1692 if (! god->generate_html) 1693 { 1694 phase_end (god, 1695 TALER_MHD_REPLY_JSON_PACK ( 1696 god->sc.con, 1697 MHD_HTTP_OK, 1698 GNUNET_JSON_pack_allow_null ( 1699 GNUNET_JSON_pack_string ("fulfillment_url", 1700 god->ct->fulfillment_url)), 1701 GNUNET_JSON_pack_bool ("refunded", 1702 god->refunded), 1703 GNUNET_JSON_pack_bool ("refund_pending", 1704 god->refund_pending), 1705 TALER_JSON_pack_amount ("refund_taken", 1706 &god->refund_taken), 1707 TALER_JSON_pack_amount ("refund_amount", 1708 &god->refund_amount))); 1709 return; 1710 } 1711 1712 if (god->refund_pending) 1713 { 1714 struct TALER_Amount refund_remaining; 1715 char *qr; 1716 char *uri; 1717 char *order_status_url; 1718 1719 GNUNET_assert (NULL != god->contract_terms_json); 1720 GNUNET_assert (NULL != god->contract_terms); 1721 GNUNET_assert (0 <= 1722 TALER_amount_subtract (&refund_remaining, 1723 &god->refund_amount, 1724 &god->refund_taken)); 1725 uri = make_taler_refund_uri (god->pc->merchant_base_url, 1726 god->order_id); 1727 order_status_url = TMH_make_order_status_url ( 1728 god->sc.con, 1729 god->order_id, 1730 god->session_id, 1731 god->hc->instance->settings.id, 1732 NULL, 1733 &god->h_contract_terms); 1734 if ( (NULL == uri) || 1735 (NULL == order_status_url) ) 1736 { 1737 GNUNET_break (0); 1738 GNUNET_free (uri); 1739 GNUNET_free (order_status_url); 1740 phase_fail (god, 1741 MHD_HTTP_BAD_REQUEST, 1742 TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED, 1743 "host"); 1744 return; 1745 } 1746 qr = TMH_create_qrcode (uri); 1747 if (NULL == qr) 1748 { 1749 GNUNET_break (0); 1750 GNUNET_free (uri); 1751 GNUNET_free (order_status_url); 1752 phase_fail (god, 1753 MHD_HTTP_INTERNAL_SERVER_ERROR, 1754 TALER_EC_GENERIC_ALLOCATION_FAILURE, 1755 "qr code"); 1756 return; 1757 } 1758 1759 { 1760 enum GNUNET_GenericReturnValue res; 1761 json_t *context; 1762 1763 context = GNUNET_JSON_PACK ( 1764 GNUNET_JSON_pack_string ("order_summary", 1765 get_order_summary (god)), 1766 TALER_JSON_pack_amount ("refund_amount", 1767 &god->refund_amount), 1768 TALER_JSON_pack_amount ("refund_taken", 1769 &god->refund_taken), 1770 TALER_JSON_pack_amount ("refund_remaining", 1771 &refund_remaining), 1772 GNUNET_JSON_pack_bool ("has_refund_taken", 1773 ! TALER_amount_is_zero ( 1774 &god->refund_taken)), 1775 GNUNET_JSON_pack_string ("order_id", 1776 god->order_id), 1777 GNUNET_JSON_pack_string ("merchant_name", 1778 god->hc->instance->settings.name), 1779 GNUNET_JSON_pack_string ("order_status_url", 1780 order_status_url), 1781 GNUNET_JSON_pack_string ("taler_refund_uri", 1782 uri), 1783 GNUNET_JSON_pack_string ("taler_refund_qrcode_svg", 1784 qr)); 1785 res = TALER_TEMPLATING_reply ( 1786 god->sc.con, 1787 MHD_HTTP_OK, 1788 "offer_refund", 1789 god->hc->instance->settings.id, 1790 uri, 1791 context); 1792 GNUNET_break (GNUNET_OK == res); 1793 json_decref (context); 1794 phase_end (god, 1795 (GNUNET_SYSERR == res) 1796 ? MHD_NO 1797 : MHD_YES); 1798 } 1799 GNUNET_free (uri); 1800 GNUNET_free (order_status_url); 1801 GNUNET_free (qr); 1802 return; 1803 } 1804 1805 { 1806 enum GNUNET_GenericReturnValue res; 1807 bool multiple_payment_options; 1808 const struct TALER_Amount *order_amount; 1809 json_t *context; 1810 const char *fulfillment_message; 1811 1812 order_amount = get_order_amount (god, 1813 &multiple_payment_options); 1814 GNUNET_break (! multiple_payment_options); 1815 fulfillment_message = get_fulfillment_message (god); 1816 1817 context = GNUNET_JSON_PACK ( 1818 GNUNET_JSON_pack_object_incref ("contract_terms", 1819 NULL != god->contract_terms_json 1820 ? god->contract_terms_json 1821 : god->order_json), 1822 GNUNET_JSON_pack_string ("order_summary", 1823 get_order_summary (god)), 1824 TALER_JSON_pack_amount ("refund_amount", 1825 &god->refund_amount), 1826 TALER_JSON_pack_amount ("refund_taken", 1827 &god->refund_taken), 1828 GNUNET_JSON_pack_allow_null ( 1829 TALER_JSON_pack_amount ("order_amount", 1830 order_amount)), 1831 GNUNET_JSON_pack_string ("order_id", 1832 god->order_id), 1833 GNUNET_JSON_pack_string ("merchant_name", 1834 god->hc->instance->settings.name), 1835 GNUNET_JSON_pack_bool ("has_refund", 1836 god->refunded), 1837 GNUNET_JSON_pack_allow_null ( 1838 GNUNET_JSON_pack_string ("fulfillment_message", 1839 fulfillment_message)), 1840 GNUNET_JSON_pack_allow_null ( 1841 GNUNET_JSON_pack_string ("fulfillment_url", 1842 god->ct->fulfillment_url))); 1843 res = TALER_TEMPLATING_reply ( 1844 god->sc.con, 1845 MHD_HTTP_OK, 1846 "show_order_details", 1847 god->hc->instance->settings.id, 1848 NULL, 1849 context); 1850 GNUNET_break (GNUNET_OK == res); 1851 json_decref (context); 1852 phase_end (god, 1853 (GNUNET_SYSERR == res) 1854 ? MHD_NO 1855 : MHD_YES); 1856 } 1857 } 1858 1859 1860 enum MHD_Result 1861 TMH_get_orders_ID (const struct TMH_RequestHandler *rh, 1862 struct MHD_Connection *connection, 1863 struct TMH_HandlerContext *hc) 1864 { 1865 struct GetOrderData *god = hc->ctx; 1866 1867 (void) rh; 1868 if (NULL == god) 1869 { 1870 god = GNUNET_new (struct GetOrderData); 1871 god->choice_index = -1; 1872 hc->ctx = god; 1873 hc->cc = &god_cleanup; 1874 god->sc.con = connection; 1875 god->hc = hc; 1876 god->order_id = hc->infix; 1877 god->generate_html 1878 = TMH_MHD_test_html_desired (connection); 1879 1880 /* first-time initialization / sanity checks */ 1881 TALER_MHD_parse_request_arg_auto (connection, 1882 "h_contract", 1883 &god->h_contract_terms, 1884 god->h_contract_provided); 1885 TALER_MHD_parse_request_arg_auto (connection, 1886 "token", 1887 &god->claim_token, 1888 god->claim_token_provided); 1889 if (! (TALER_MHD_arg_to_yna (connection, 1890 "allow_refunded_for_repurchase", 1891 TALER_EXCHANGE_YNA_NO, 1892 &god->allow_refunded_for_repurchase)) ) 1893 return TALER_MHD_reply_with_error (connection, 1894 MHD_HTTP_BAD_REQUEST, 1895 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1896 "allow_refunded_for_repurchase"); 1897 god->session_id = MHD_lookup_connection_value (connection, 1898 MHD_GET_ARGUMENT_KIND, 1899 "session_id"); 1900 1901 /* process await_refund_obtained argument */ 1902 { 1903 const char *await_refund_obtained_s; 1904 1905 await_refund_obtained_s = 1906 MHD_lookup_connection_value (connection, 1907 MHD_GET_ARGUMENT_KIND, 1908 "await_refund_obtained"); 1909 god->sc.awaiting_refund_obtained = 1910 (NULL != await_refund_obtained_s) 1911 ? 0 == strcasecmp (await_refund_obtained_s, 1912 "yes") 1913 : false; 1914 if (god->sc.awaiting_refund_obtained) 1915 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1916 "Awaiting refund obtained\n"); 1917 } 1918 1919 TALER_MHD_parse_request_amount (connection, 1920 "refund", 1921 &god->sc.refund_expected); 1922 if (TALER_amount_is_valid (&god->sc.refund_expected)) 1923 { 1924 god->sc.awaiting_refund = true; 1925 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1926 "Awaiting minimum refund of %s\n", 1927 TALER_amount2s (&god->sc.refund_expected)); 1928 } 1929 TALER_MHD_parse_request_timeout (connection, 1930 &god->sc.long_poll_timeout); 1931 } 1932 1933 if (GNUNET_SYSERR == god->suspended) 1934 return MHD_NO; /* we are in shutdown */ 1935 if (GNUNET_YES == god->suspended) 1936 { 1937 god->suspended = GNUNET_NO; 1938 GNUNET_CONTAINER_DLL_remove (god_head, 1939 god_tail, 1940 god); 1941 } 1942 1943 while (1) 1944 { 1945 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1946 "Handling request in phase %d\n", 1947 (int) god->phase); 1948 switch (god->phase) 1949 { 1950 case GOP_INIT: 1951 phase_init (god); 1952 break; 1953 case GOP_LOOKUP_TERMS: 1954 phase_lookup_terms (god); 1955 break; 1956 case GOP_PARSE_CONTRACT: 1957 phase_parse_contract (god); 1958 break; 1959 case GOP_CHECK_CLIENT_ACCESS: 1960 phase_check_client_access (god); 1961 break; 1962 case GOP_CHECK_PAID: 1963 phase_check_paid (god); 1964 break; 1965 case GOP_REDIRECT_TO_PAID_ORDER: 1966 if (phase_redirect_to_paid_order (god)) 1967 return MHD_YES; 1968 break; 1969 case GOP_HANDLE_UNPAID: 1970 if (phase_handle_unpaid (god)) 1971 return MHD_YES; 1972 break; 1973 case GOP_CHECK_REFUNDED: 1974 if (phase_check_refunded (god)) 1975 return MHD_YES; 1976 break; 1977 case GOP_RETURN_STATUS: 1978 phase_return_status (god); 1979 break; 1980 case GOP_RETURN_MHD_YES: 1981 return MHD_YES; 1982 case GOP_RETURN_MHD_NO: 1983 return MHD_NO; 1984 } 1985 } 1986 } 1987 1988 1989 /* end of taler-merchant-httpd_get-orders-ORDER_ID.c */