taler-merchant-httpd_get-orders-ORDER_ID.c (52935B)
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/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 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_get_contract_terms_status ( 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 "get_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_get_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 "get_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 (! god->generate_html) && 1054 (NULL == already_paid_order_id) ) 1055 { 1056 /* long polling: do not queue a response, suspend connection instead */ 1057 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1058 "Suspending request: long polling for payment\n"); 1059 suspend_god (god); 1060 return true; 1061 } 1062 /* Check if resource_id has been paid for in the same session 1063 * with another order_id. 1064 */ 1065 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1066 "Sending payment request\n"); 1067 taler_pay_uri = TMH_make_taler_pay_uri ( 1068 god->sc.con, 1069 god->order_id, 1070 god->session_id, 1071 god->hc->instance->settings.id, 1072 &god->claim_token); 1073 order_status_url = TMH_make_order_status_url ( 1074 god->sc.con, 1075 god->order_id, 1076 god->session_id, 1077 god->hc->instance->settings.id, 1078 &god->claim_token, 1079 NULL); 1080 if ( (NULL == taler_pay_uri) || 1081 (NULL == order_status_url) ) 1082 { 1083 GNUNET_break_op (0); 1084 GNUNET_free (taler_pay_uri); 1085 GNUNET_free (order_status_url); 1086 phase_fail (god, 1087 MHD_HTTP_BAD_REQUEST, 1088 TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED, 1089 "host"); 1090 return false; 1091 } 1092 if (god->generate_html) 1093 { 1094 if (NULL != already_paid_order_id) 1095 { 1096 struct MHD_Response *reply; 1097 1098 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1099 "Redirecting to already paid order %s via fulfillment URL %s\n", 1100 already_paid_order_id, 1101 god->ct->fulfillment_url); 1102 reply = MHD_create_response_from_buffer (0, 1103 NULL, 1104 MHD_RESPMEM_PERSISTENT); 1105 if (NULL == reply) 1106 { 1107 GNUNET_break (0); 1108 phase_end (god, 1109 MHD_NO); 1110 return false; 1111 } 1112 GNUNET_break (MHD_YES == 1113 MHD_add_response_header ( 1114 reply, 1115 MHD_HTTP_HEADER_LOCATION, 1116 god->ct->fulfillment_url)); 1117 { 1118 ret = MHD_queue_response (god->sc.con, 1119 MHD_HTTP_FOUND, 1120 reply); 1121 MHD_destroy_response (reply); 1122 phase_end (god, 1123 ret); 1124 return false; 1125 } 1126 } 1127 1128 { 1129 char *qr; 1130 1131 qr = TMH_create_qrcode (taler_pay_uri); 1132 if (NULL == qr) 1133 { 1134 GNUNET_break (0); 1135 phase_end (god, 1136 MHD_NO); 1137 return false; 1138 } 1139 { 1140 enum GNUNET_GenericReturnValue res; 1141 json_t *context; 1142 1143 context = GNUNET_JSON_PACK ( 1144 GNUNET_JSON_pack_string ("taler_pay_uri", 1145 taler_pay_uri), 1146 GNUNET_JSON_pack_string ("order_status_url", 1147 order_status_url), 1148 GNUNET_JSON_pack_string ("taler_pay_qrcode_svg", 1149 qr), 1150 GNUNET_JSON_pack_string ("order_summary", 1151 get_order_summary (god))); 1152 res = TALER_TEMPLATING_reply ( 1153 god->sc.con, 1154 MHD_HTTP_PAYMENT_REQUIRED, 1155 "request_payment", 1156 god->hc->instance->settings.id, 1157 taler_pay_uri, 1158 context); 1159 if (GNUNET_SYSERR == res) 1160 { 1161 GNUNET_break (0); 1162 ret = MHD_NO; 1163 } 1164 else 1165 { 1166 ret = MHD_YES; 1167 } 1168 json_decref (context); 1169 } 1170 GNUNET_free (qr); 1171 } 1172 } 1173 else /* end of 'generate HTML' */ 1174 { 1175 ret = TALER_MHD_REPLY_JSON_PACK ( 1176 god->sc.con, 1177 MHD_HTTP_PAYMENT_REQUIRED, 1178 GNUNET_JSON_pack_string ("taler_pay_uri", 1179 taler_pay_uri), 1180 GNUNET_JSON_pack_allow_null ( 1181 GNUNET_JSON_pack_string ("fulfillment_url", 1182 god->ct->fulfillment_url)), 1183 GNUNET_JSON_pack_allow_null ( 1184 GNUNET_JSON_pack_string ("already_paid_order_id", 1185 already_paid_order_id))); 1186 } 1187 GNUNET_free (taler_pay_uri); 1188 GNUNET_free (order_status_url); 1189 phase_end (god, 1190 ret); 1191 return false; 1192 } 1193 1194 1195 /** 1196 * Check if the order has been paid. 1197 * 1198 * @param[in,out] god request context 1199 */ 1200 static void 1201 phase_check_paid (struct GetOrderData *god) 1202 { 1203 enum GNUNET_DB_QueryStatus qs; 1204 struct TALER_PrivateContractHashP h_contract; 1205 1206 god->paid = false; 1207 qs = TALER_MERCHANTDB_get_order_status ( 1208 TMH_db, 1209 god->hc->instance->settings.id, 1210 god->order_id, 1211 &h_contract, 1212 &god->paid); 1213 if (0 > qs) 1214 { 1215 /* Always report on hard error as well to enable diagnostics */ 1216 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 1217 phase_fail (god, 1218 MHD_HTTP_INTERNAL_SERVER_ERROR, 1219 TALER_EC_GENERIC_DB_FETCH_FAILED, 1220 "get_order_status"); 1221 return; 1222 } 1223 god->phase++; 1224 } 1225 1226 1227 /** 1228 * Check if the client already paid for an equivalent 1229 * order under this session, and if so redirect to 1230 * that order. 1231 * 1232 * @param[in,out] god request context 1233 * @return true to exit due to suspension 1234 */ 1235 static bool 1236 phase_redirect_to_paid_order (struct GetOrderData *god) 1237 { 1238 if ( (NULL != god->session_id) && 1239 (NULL != god->ct->fulfillment_url) ) 1240 { 1241 /* Check if client paid for this fulfillment article 1242 already within this session, but using a different 1243 order ID. If so, redirect the client to the order 1244 it already paid. Allows, for example, the case 1245 where a mobile phone pays for a browser's session, 1246 where the mobile phone has a different order 1247 ID (because it purchased the article earlier) 1248 than the one that the browser is waiting for. */ 1249 char *already_paid_order_id = NULL; 1250 enum GNUNET_DB_QueryStatus qs; 1251 1252 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1253 "Running re-purchase detection for %s/%s\n", 1254 god->session_id, 1255 god->ct->fulfillment_url); 1256 qs = TALER_MERCHANTDB_get_order_by_fulfillment ( 1257 TMH_db, 1258 god->hc->instance->settings.id, 1259 god->ct->fulfillment_url, 1260 god->session_id, 1261 TALER_EXCHANGE_YNA_NO != god->allow_refunded_for_repurchase, 1262 &already_paid_order_id); 1263 if (qs < 0) 1264 { 1265 /* single, read-only SQL statements should never cause 1266 serialization problems */ 1267 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 1268 /* Always report on hard error as well to enable diagnostics */ 1269 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 1270 phase_fail (god, 1271 MHD_HTTP_INTERNAL_SERVER_ERROR, 1272 TALER_EC_GENERIC_DB_FETCH_FAILED, 1273 "order by fulfillment"); 1274 return false; 1275 } 1276 if ( (! god->paid) && 1277 ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) || 1278 (0 != strcmp (god->order_id, 1279 already_paid_order_id)) ) ) 1280 { 1281 bool ret; 1282 1283 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1284 "Sending pay request for order %s (already paid: %s)\n", 1285 god->order_id, 1286 already_paid_order_id); 1287 ret = send_pay_request (god, 1288 already_paid_order_id); 1289 GNUNET_free (already_paid_order_id); 1290 return ret; 1291 } 1292 GNUNET_free (already_paid_order_id); 1293 } 1294 god->phase++; 1295 return false; 1296 } 1297 1298 1299 /** 1300 * Check if the order has been paid, and if not 1301 * request payment. 1302 * 1303 * @param[in,out] god request context 1304 * @return true to exit due to suspension 1305 */ 1306 static bool 1307 phase_handle_unpaid (struct GetOrderData *god) 1308 { 1309 if (god->paid) 1310 { 1311 god->phase++; 1312 return false; 1313 } 1314 if (god->claimed) 1315 { 1316 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1317 "Order claimed but unpaid, sending pay request for order %s\n", 1318 god->order_id); 1319 } 1320 else 1321 { 1322 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1323 "Order unclaimed, sending pay request for order %s\n", 1324 god->order_id); 1325 } 1326 return send_pay_request (god, 1327 NULL); 1328 } 1329 1330 1331 /** 1332 * Function called with detailed information about a refund. 1333 * It is responsible for packing up the data to return. 1334 * 1335 * @param cls closure 1336 * @param refund_serial unique serial number of the refund 1337 * @param timestamp time of the refund (for grouping of refunds in the wallet UI) 1338 * @param coin_pub public coin from which the refund comes from 1339 * @param exchange_url URL of the exchange that issued @a coin_pub 1340 * @param rtransaction_id identificator of the refund 1341 * @param reason human-readable explanation of the refund 1342 * @param refund_amount refund amount which is being taken from @a coin_pub 1343 * @param pending true if the this refund was not yet processed by the wallet/exchange 1344 */ 1345 static void 1346 process_refunds_cb (void *cls, 1347 uint64_t refund_serial, 1348 struct GNUNET_TIME_Timestamp timestamp, 1349 const struct TALER_CoinSpendPublicKeyP *coin_pub, 1350 const char *exchange_url, 1351 uint64_t rtransaction_id, 1352 const char *reason, 1353 const struct TALER_Amount *refund_amount, 1354 bool pending) 1355 { 1356 struct GetOrderData *god = cls; 1357 1358 (void) refund_serial; 1359 (void) timestamp; 1360 (void) exchange_url; 1361 (void) rtransaction_id; 1362 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1363 "Found refund of %s for coin %s with reason `%s' in database\n", 1364 TALER_amount2s (refund_amount), 1365 TALER_B2S (coin_pub), 1366 reason); 1367 god->refund_pending |= pending; 1368 if ( (GNUNET_OK != 1369 TALER_amount_cmp_currency (&god->refund_taken, 1370 refund_amount)) || 1371 (GNUNET_OK != 1372 TALER_amount_cmp_currency (&god->refund_amount, 1373 refund_amount)) ) 1374 { 1375 god->bad_refund_currency_in_db = true; 1376 return; 1377 } 1378 if (! pending) 1379 { 1380 GNUNET_assert (0 <= 1381 TALER_amount_add (&god->refund_taken, 1382 &god->refund_taken, 1383 refund_amount)); 1384 } 1385 GNUNET_assert (0 <= 1386 TALER_amount_add (&god->refund_amount, 1387 &god->refund_amount, 1388 refund_amount)); 1389 god->refunded = true; 1390 } 1391 1392 1393 /** 1394 * Check if the order has been refunded. 1395 * 1396 * @param[in,out] god request context 1397 * @return true to exit due to suspension 1398 */ 1399 static bool 1400 phase_check_refunded (struct GetOrderData *god) 1401 { 1402 enum GNUNET_DB_QueryStatus qs; 1403 struct TALER_Amount refund_amount; 1404 const char *refund_currency; 1405 1406 GNUNET_assert (NULL != god->contract_terms); 1407 switch (god->ct->version) 1408 { 1409 case TALER_MERCHANT_CONTRACT_VERSION_0: 1410 refund_amount = god->pc->details.v0.brutto; 1411 refund_currency = god->pc->details.v0.brutto.currency; 1412 break; 1413 case TALER_MERCHANT_CONTRACT_VERSION_1: 1414 if (god->choice_index < 0) 1415 { 1416 // order was not paid, no refund to be checked 1417 god->phase++; 1418 return false; 1419 } 1420 GNUNET_assert (god->choice_index < 1421 god->pc->details.v1.choices_len); 1422 refund_currency = god->pc->details.v1.choices[ 1423 god->choice_index].amount.currency; 1424 GNUNET_assert (GNUNET_OK == 1425 TALER_amount_set_zero (refund_currency, 1426 &refund_amount)); 1427 break; 1428 default: 1429 { 1430 GNUNET_break (0); 1431 phase_fail (god, 1432 MHD_HTTP_INTERNAL_SERVER_ERROR, 1433 TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION, 1434 NULL); 1435 return false; 1436 } 1437 } 1438 1439 if ( (god->sc.awaiting_refund) && 1440 (GNUNET_OK != 1441 TALER_amount_cmp_currency (&refund_amount, 1442 &god->sc.refund_expected)) ) 1443 { 1444 GNUNET_break (0); 1445 phase_fail (god, 1446 MHD_HTTP_CONFLICT, 1447 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1448 refund_currency); 1449 return false; 1450 } 1451 1452 /* At this point, we know the contract was paid. Let's check for 1453 refunds. First, clear away refunds found from previous invocations. */ 1454 GNUNET_assert (GNUNET_OK == 1455 TALER_amount_set_zero (refund_currency, 1456 &god->refund_amount)); 1457 GNUNET_assert (GNUNET_OK == 1458 TALER_amount_set_zero (refund_currency, 1459 &god->refund_taken)); 1460 qs = TALER_MERCHANTDB_iterate_refunds_detailed ( 1461 TMH_db, 1462 god->hc->instance->settings.id, 1463 &god->h_contract_terms, 1464 &process_refunds_cb, 1465 god); 1466 if (0 > qs) 1467 { 1468 GNUNET_break (0); 1469 phase_fail (god, 1470 MHD_HTTP_INTERNAL_SERVER_ERROR, 1471 TALER_EC_GENERIC_DB_FETCH_FAILED, 1472 "iterate_refunds_detailed"); 1473 return false; 1474 } 1475 if (god->bad_refund_currency_in_db) 1476 { 1477 GNUNET_break (0); 1478 phase_fail (god, 1479 MHD_HTTP_INTERNAL_SERVER_ERROR, 1480 TALER_EC_GENERIC_DB_FETCH_FAILED, 1481 "currency mix-up between contract price and refunds in database"); 1482 return false; 1483 } 1484 if ( ((god->sc.awaiting_refund) && 1485 ( (! god->refunded) || 1486 (1 != TALER_amount_cmp (&god->refund_amount, 1487 &god->sc.refund_expected)) )) || 1488 ( (god->sc.awaiting_refund_obtained) && 1489 (god->refund_pending) ) ) 1490 { 1491 /* Client is waiting for a refund larger than what we have, suspend 1492 until timeout */ 1493 struct GNUNET_TIME_Relative remaining; 1494 1495 remaining = GNUNET_TIME_absolute_get_remaining (god->sc.long_poll_timeout); 1496 if ( (! GNUNET_TIME_relative_is_zero (remaining)) && 1497 (! god->generate_html) ) 1498 { 1499 /* yes, indeed suspend */ 1500 if (god->sc.awaiting_refund) 1501 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1502 "Awaiting refund exceeding %s\n", 1503 TALER_amount2s (&god->sc.refund_expected)); 1504 if (god->sc.awaiting_refund_obtained) 1505 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1506 "Awaiting pending refunds\n"); 1507 suspend_god (god); 1508 return true; 1509 } 1510 } 1511 god->phase++; 1512 return false; 1513 } 1514 1515 1516 /** 1517 * Create a taler://refund/ URI for the given @a con and @a order_id 1518 * and @a instance_id. 1519 * 1520 * @param merchant_base_url URL to take host and path from; 1521 * we cannot take it from the MHD connection as a browser 1522 * may have changed 'http' to 'https' and we MUST be consistent 1523 * with what the merchant's frontend used initially 1524 * @param order_id the order id 1525 * @return corresponding taler://refund/ URI, or NULL on missing "host" 1526 */ 1527 static char * 1528 make_taler_refund_uri (const char *merchant_base_url, 1529 const char *order_id) 1530 { 1531 struct GNUNET_Buffer buf = { 0 }; 1532 char *url; 1533 struct GNUNET_Uri uri; 1534 1535 url = GNUNET_strdup (merchant_base_url); 1536 if (-1 == GNUNET_uri_parse (&uri, 1537 url)) 1538 { 1539 GNUNET_break (0); 1540 GNUNET_free (url); 1541 return NULL; 1542 } 1543 GNUNET_assert (NULL != order_id); 1544 GNUNET_buffer_write_str (&buf, 1545 "taler"); 1546 if (0 == strcasecmp ("http", 1547 uri.scheme)) 1548 GNUNET_buffer_write_str (&buf, 1549 "+http"); 1550 GNUNET_buffer_write_str (&buf, 1551 "://refund/"); 1552 GNUNET_buffer_write_str (&buf, 1553 uri.host); 1554 if (0 != uri.port) 1555 GNUNET_buffer_write_fstr (&buf, 1556 ":%u", 1557 (unsigned int) uri.port); 1558 if (NULL != uri.path) 1559 GNUNET_buffer_write_path (&buf, 1560 uri.path); 1561 GNUNET_buffer_write_path (&buf, 1562 order_id); 1563 GNUNET_buffer_write_path (&buf, 1564 ""); // Trailing slash 1565 GNUNET_free (url); 1566 return GNUNET_buffer_reap_str (&buf); 1567 } 1568 1569 1570 /** 1571 * Generate the order status response. 1572 * 1573 * @param[in,out] god request context 1574 */ 1575 static void 1576 phase_return_status (struct GetOrderData *god) 1577 { 1578 /* All operations done, build final response */ 1579 if (! god->generate_html) 1580 { 1581 phase_end (god, 1582 TALER_MHD_REPLY_JSON_PACK ( 1583 god->sc.con, 1584 MHD_HTTP_OK, 1585 GNUNET_JSON_pack_allow_null ( 1586 GNUNET_JSON_pack_string ("fulfillment_url", 1587 god->ct->fulfillment_url)), 1588 GNUNET_JSON_pack_bool ("refunded", 1589 god->refunded), 1590 GNUNET_JSON_pack_bool ("refund_pending", 1591 god->refund_pending), 1592 TALER_JSON_pack_amount ("refund_taken", 1593 &god->refund_taken), 1594 TALER_JSON_pack_amount ("refund_amount", 1595 &god->refund_amount))); 1596 return; 1597 } 1598 1599 if (god->refund_pending) 1600 { 1601 char *qr; 1602 char *uri; 1603 1604 GNUNET_assert (NULL != god->contract_terms_json); 1605 GNUNET_assert (NULL != god->contract_terms); 1606 uri = make_taler_refund_uri (god->pc->merchant_base_url, 1607 god->order_id); 1608 if (NULL == uri) 1609 { 1610 GNUNET_break (0); 1611 phase_fail (god, 1612 MHD_HTTP_INTERNAL_SERVER_ERROR, 1613 TALER_EC_GENERIC_ALLOCATION_FAILURE, 1614 "refund URI"); 1615 return; 1616 } 1617 qr = TMH_create_qrcode (uri); 1618 if (NULL == qr) 1619 { 1620 GNUNET_break (0); 1621 GNUNET_free (uri); 1622 phase_fail (god, 1623 MHD_HTTP_INTERNAL_SERVER_ERROR, 1624 TALER_EC_GENERIC_ALLOCATION_FAILURE, 1625 "qr code"); 1626 return; 1627 } 1628 1629 { 1630 enum GNUNET_GenericReturnValue res; 1631 json_t *context; 1632 1633 context = GNUNET_JSON_PACK ( 1634 GNUNET_JSON_pack_string ("order_summary", 1635 get_order_summary (god)), 1636 TALER_JSON_pack_amount ("refund_amount", 1637 &god->refund_amount), 1638 TALER_JSON_pack_amount ("refund_taken", 1639 &god->refund_taken), 1640 GNUNET_JSON_pack_string ("taler_refund_uri", 1641 uri), 1642 GNUNET_JSON_pack_string ("taler_refund_qrcode_svg", 1643 qr)); 1644 res = TALER_TEMPLATING_reply ( 1645 god->sc.con, 1646 MHD_HTTP_OK, 1647 "offer_refund", 1648 god->hc->instance->settings.id, 1649 uri, 1650 context); 1651 GNUNET_break (GNUNET_OK == res); 1652 json_decref (context); 1653 phase_end (god, 1654 (GNUNET_SYSERR == res) 1655 ? MHD_NO 1656 : MHD_YES); 1657 } 1658 GNUNET_free (uri); 1659 GNUNET_free (qr); 1660 return; 1661 } 1662 1663 { 1664 enum GNUNET_GenericReturnValue res; 1665 json_t *context; 1666 1667 context = GNUNET_JSON_PACK ( 1668 GNUNET_JSON_pack_object_incref ("contract_terms", 1669 NULL != god->contract_terms_json 1670 ? god->contract_terms_json 1671 : god->order_json), 1672 GNUNET_JSON_pack_string ("order_summary", 1673 get_order_summary (god)), 1674 TALER_JSON_pack_amount ("refund_amount", 1675 &god->refund_amount), 1676 TALER_JSON_pack_amount ("refund_taken", 1677 &god->refund_taken)); 1678 res = TALER_TEMPLATING_reply ( 1679 god->sc.con, 1680 MHD_HTTP_OK, 1681 "show_order_details", 1682 god->hc->instance->settings.id, 1683 NULL, 1684 context); 1685 GNUNET_break (GNUNET_OK == res); 1686 json_decref (context); 1687 phase_end (god, 1688 (GNUNET_SYSERR == res) 1689 ? MHD_NO 1690 : MHD_YES); 1691 } 1692 } 1693 1694 1695 enum MHD_Result 1696 TMH_get_orders_ID (const struct TMH_RequestHandler *rh, 1697 struct MHD_Connection *connection, 1698 struct TMH_HandlerContext *hc) 1699 { 1700 struct GetOrderData *god = hc->ctx; 1701 1702 (void) rh; 1703 if (NULL == god) 1704 { 1705 god = GNUNET_new (struct GetOrderData); 1706 hc->ctx = god; 1707 hc->cc = &god_cleanup; 1708 god->sc.con = connection; 1709 god->hc = hc; 1710 god->order_id = hc->infix; 1711 god->generate_html 1712 = TMH_MHD_test_html_desired (connection); 1713 1714 /* first-time initialization / sanity checks */ 1715 TALER_MHD_parse_request_arg_auto (connection, 1716 "h_contract", 1717 &god->h_contract_terms, 1718 god->h_contract_provided); 1719 TALER_MHD_parse_request_arg_auto (connection, 1720 "token", 1721 &god->claim_token, 1722 god->claim_token_provided); 1723 if (! (TALER_MHD_arg_to_yna (connection, 1724 "allow_refunded_for_repurchase", 1725 TALER_EXCHANGE_YNA_NO, 1726 &god->allow_refunded_for_repurchase)) ) 1727 return TALER_MHD_reply_with_error (connection, 1728 MHD_HTTP_BAD_REQUEST, 1729 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1730 "allow_refunded_for_repurchase"); 1731 god->session_id = MHD_lookup_connection_value (connection, 1732 MHD_GET_ARGUMENT_KIND, 1733 "session_id"); 1734 1735 /* process await_refund_obtained argument */ 1736 { 1737 const char *await_refund_obtained_s; 1738 1739 await_refund_obtained_s = 1740 MHD_lookup_connection_value (connection, 1741 MHD_GET_ARGUMENT_KIND, 1742 "await_refund_obtained"); 1743 god->sc.awaiting_refund_obtained = 1744 (NULL != await_refund_obtained_s) 1745 ? 0 == strcasecmp (await_refund_obtained_s, 1746 "yes") 1747 : false; 1748 if (god->sc.awaiting_refund_obtained) 1749 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1750 "Awaiting refund obtained\n"); 1751 } 1752 1753 TALER_MHD_parse_request_amount (connection, 1754 "refund", 1755 &god->sc.refund_expected); 1756 if (TALER_amount_is_valid (&god->sc.refund_expected)) 1757 { 1758 god->sc.awaiting_refund = true; 1759 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1760 "Awaiting minimum refund of %s\n", 1761 TALER_amount2s (&god->sc.refund_expected)); 1762 } 1763 TALER_MHD_parse_request_timeout (connection, 1764 &god->sc.long_poll_timeout); 1765 } 1766 1767 if (GNUNET_SYSERR == god->suspended) 1768 return MHD_NO; /* we are in shutdown */ 1769 if (GNUNET_YES == god->suspended) 1770 { 1771 god->suspended = GNUNET_NO; 1772 GNUNET_CONTAINER_DLL_remove (god_head, 1773 god_tail, 1774 god); 1775 } 1776 1777 while (1) 1778 { 1779 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1780 "Handling request in phase %d\n", 1781 (int) god->phase); 1782 switch (god->phase) 1783 { 1784 case GOP_INIT: 1785 phase_init (god); 1786 break; 1787 case GOP_LOOKUP_TERMS: 1788 phase_lookup_terms (god); 1789 break; 1790 case GOP_PARSE_CONTRACT: 1791 phase_parse_contract (god); 1792 break; 1793 case GOP_CHECK_CLIENT_ACCESS: 1794 phase_check_client_access (god); 1795 break; 1796 case GOP_CHECK_PAID: 1797 phase_check_paid (god); 1798 break; 1799 case GOP_REDIRECT_TO_PAID_ORDER: 1800 if (phase_redirect_to_paid_order (god)) 1801 return MHD_YES; 1802 break; 1803 case GOP_HANDLE_UNPAID: 1804 if (phase_handle_unpaid (god)) 1805 return MHD_YES; 1806 break; 1807 case GOP_CHECK_REFUNDED: 1808 if (phase_check_refunded (god)) 1809 return MHD_YES; 1810 break; 1811 case GOP_RETURN_STATUS: 1812 phase_return_status (god); 1813 break; 1814 case GOP_RETURN_MHD_YES: 1815 return MHD_YES; 1816 case GOP_RETURN_MHD_NO: 1817 return MHD_NO; 1818 } 1819 } 1820 } 1821 1822 1823 /* end of taler-merchant-httpd_get-orders-ORDER_ID.c */