taler-merchant-httpd_post-orders-ORDER_ID-abort.c (32193B)
1 /* 2 This file is part of TALER 3 (C) 2014-2021 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Affero General Public License as 7 published by the Free Software Foundation; either version 3, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 /** 20 * @file src/backend/taler-merchant-httpd_post-orders-ORDER_ID-abort.c 21 * @brief handling of POST /orders/$ID/abort requests 22 * @author Marcello Stanisci 23 * @author Christian Grothoff 24 * @author Florian Dold 25 */ 26 #include "platform.h" 27 struct RefundDetails; 28 #define TALER_EXCHANGE_POST_COINS_REFUND_RESULT_CLOSURE struct RefundDetails 29 #include <taler/taler_json_lib.h> 30 #include <taler/taler_exchange_service.h> 31 #include "taler-merchant-httpd_exchanges.h" 32 #include "taler-merchant-httpd_get-exchanges.h" 33 #include "taler-merchant-httpd_helper.h" 34 #include "taler-merchant-httpd_post-orders-ORDER_ID-abort.h" 35 #include "merchant-database/lookup_deposits.h" 36 #include "merchant-database/lookup_order_status.h" 37 #include "merchant-database/refund_coin.h" 38 #include "merchant-database/preflight.h" 39 #include "merchant-database/start.h" 40 #include "merchant-database/set_instance.h" 41 42 43 /** 44 * How long to wait before giving up processing with the exchange? 45 */ 46 #define ABORT_GENERIC_TIMEOUT (GNUNET_TIME_relative_multiply ( \ 47 GNUNET_TIME_UNIT_SECONDS, \ 48 30)) 49 50 /** 51 * How often do we retry the (complex!) database transaction? 52 */ 53 #define MAX_RETRIES 5 54 55 /** 56 * Information we keep for an individual call to the /abort handler. 57 */ 58 struct AbortContext; 59 60 /** 61 * Information kept during a /abort request for each coin. 62 */ 63 struct RefundDetails 64 { 65 66 /** 67 * Public key of the coin. 68 */ 69 struct TALER_CoinSpendPublicKeyP coin_pub; 70 71 /** 72 * Signature from the exchange confirming the refund. 73 * Set if we were successful (status 200). 74 */ 75 struct TALER_ExchangeSignatureP exchange_sig; 76 77 /** 78 * Public key used for @e exchange_sig. 79 * Set if we were successful (status 200). 80 */ 81 struct TALER_ExchangePublicKeyP exchange_pub; 82 83 /** 84 * Reference to the main AbortContext 85 */ 86 struct AbortContext *ac; 87 88 /** 89 * Handle to the refund operation we are performing for 90 * this coin, NULL after the operation is done. 91 */ 92 struct TALER_EXCHANGE_PostCoinsRefundHandle *rh; 93 94 /** 95 * URL of the exchange that issued this coin. 96 */ 97 char *exchange_url; 98 99 /** 100 * Body of the response from the exchange. Note that the body returned MUST 101 * be freed (if non-NULL). 102 */ 103 json_t *exchange_reply; 104 105 /** 106 * Amount this coin contributes to the total purchase price. 107 * This amount includes the deposit fee. 108 */ 109 struct TALER_Amount amount_with_fee; 110 111 /** 112 * Offset of this coin into the `rd` array of all coins in the 113 * @e ac. 114 */ 115 unsigned int index; 116 117 /** 118 * HTTP status returned by the exchange (if any). 119 */ 120 unsigned int http_status; 121 122 /** 123 * Did we try to process this refund yet? 124 */ 125 bool processed; 126 127 /** 128 * Did we find the deposit in our own database? 129 */ 130 bool found_deposit; 131 }; 132 133 134 /** 135 * Information we keep for an individual call to the /abort handler. 136 */ 137 struct AbortContext 138 { 139 140 /** 141 * Hashed contract terms (according to client). 142 */ 143 struct TALER_PrivateContractHashP h_contract_terms; 144 145 /** 146 * Context for our operation. 147 */ 148 struct TMH_HandlerContext *hc; 149 150 /** 151 * Stored in a DLL. 152 */ 153 struct AbortContext *next; 154 155 /** 156 * Stored in a DLL. 157 */ 158 struct AbortContext *prev; 159 160 /** 161 * Array with @e coins_cnt coins we are despositing. 162 */ 163 struct RefundDetails *rd; 164 165 /** 166 * MHD connection to return to 167 */ 168 struct MHD_Connection *connection; 169 170 /** 171 * Task called when the (suspended) processing for 172 * the /abort request times out. 173 * Happens when we don't get a response from the exchange. 174 */ 175 struct GNUNET_SCHEDULER_Task *timeout_task; 176 177 /** 178 * Response to return, NULL if we don't have one yet. 179 */ 180 struct MHD_Response *response; 181 182 /** 183 * Handle to the exchange that we are doing the abortment with. 184 * (initially NULL while @e fo is trying to find a exchange). 185 */ 186 struct TALER_EXCHANGE_Handle *mh; 187 188 /** 189 * Handle for operation to lookup /keys (and auditors) from 190 * the exchange used for this transaction; NULL if no operation is 191 * pending. 192 */ 193 struct TMH_EXCHANGES_KeysOperation *fo; 194 195 /** 196 * URL of the exchange used for the last @e fo. 197 */ 198 const char *current_exchange; 199 200 /** 201 * Status of the database operations performed by the 202 * #refund_coins() row callback. Set to 203 * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT before starting the 204 * iteration over the deposits, and to the failed query status if 205 * one of the refund_coin() operations failed. Inspected by 206 * #begin_transaction() once the iteration has finished. 207 */ 208 enum GNUNET_DB_QueryStatus row_qs; 209 210 /** 211 * Number of coins this abort is for. Length of the @e rd array. 212 */ 213 size_t coins_cnt; 214 215 /** 216 * How often have we retried the 'main' transaction? 217 */ 218 unsigned int retry_counter; 219 220 /** 221 * Number of transactions still pending. Initially set to 222 * @e coins_cnt, decremented on each transaction that 223 * successfully finished. 224 */ 225 size_t pending; 226 227 /** 228 * Number of transactions still pending for the currently selected 229 * exchange. Initially set to the number of coins started at the 230 * exchange, decremented on each transaction that successfully 231 * finished. Once it hits zero, we pick the next exchange. 232 */ 233 size_t pending_at_ce; 234 235 /** 236 * HTTP status code to use for the reply, i.e 200 for "OK". 237 * Special value UINT_MAX is used to indicate hard errors 238 * (no reply, return #MHD_NO). 239 */ 240 unsigned int response_code; 241 242 /** 243 * #GNUNET_NO if the @e connection was not suspended, 244 * #GNUNET_YES if the @e connection was suspended, 245 * #GNUNET_SYSERR if @e connection was resumed to as 246 * part of #MH_force_ac_resume during shutdown. 247 */ 248 int suspended; 249 250 }; 251 252 253 /** 254 * Head of active abort context DLL. 255 */ 256 static struct AbortContext *ac_head; 257 258 /** 259 * Tail of active abort context DLL. 260 */ 261 static struct AbortContext *ac_tail; 262 263 264 /** 265 * Abort all pending /deposit operations. 266 * 267 * @param ac abort context to abort 268 */ 269 static void 270 abort_refunds (struct AbortContext *ac) 271 { 272 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 273 "Aborting pending /deposit operations\n"); 274 for (size_t i = 0; i<ac->coins_cnt; i++) 275 { 276 struct RefundDetails *rdi = &ac->rd[i]; 277 278 if (NULL != rdi->rh) 279 { 280 TALER_EXCHANGE_post_coins_refund_cancel (rdi->rh); 281 rdi->rh = NULL; 282 } 283 } 284 } 285 286 287 void 288 TMH_force_ac_resume () 289 { 290 for (struct AbortContext *ac = ac_head; 291 NULL != ac; 292 ac = ac->next) 293 { 294 abort_refunds (ac); 295 if (NULL != ac->timeout_task) 296 { 297 GNUNET_SCHEDULER_cancel (ac->timeout_task); 298 ac->timeout_task = NULL; 299 } 300 if (GNUNET_YES == ac->suspended) 301 { 302 ac->suspended = GNUNET_SYSERR; 303 MHD_resume_connection (ac->connection); 304 } 305 } 306 } 307 308 309 /** 310 * Resume the given abort context and send the given response. 311 * Stores the response in the @a ac and signals MHD to resume 312 * the connection. Also ensures MHD runs immediately. 313 * 314 * @param ac abortment context 315 * @param response_code response code to use 316 * @param response response data to send back 317 */ 318 static void 319 resume_abort_with_response (struct AbortContext *ac, 320 unsigned int response_code, 321 struct MHD_Response *response) 322 { 323 abort_refunds (ac); 324 ac->response_code = response_code; 325 ac->response = response; 326 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 327 "Resuming /abort handling as exchange interaction is done (%u)\n", 328 response_code); 329 if (NULL != ac->timeout_task) 330 { 331 GNUNET_SCHEDULER_cancel (ac->timeout_task); 332 ac->timeout_task = NULL; 333 } 334 GNUNET_assert (GNUNET_YES == ac->suspended); 335 ac->suspended = GNUNET_NO; 336 MHD_resume_connection (ac->connection); 337 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 338 } 339 340 341 /** 342 * Resume abortment processing with an error. 343 * 344 * @param ac operation to resume 345 * @param http_status http status code to return 346 * @param ec taler error code to return 347 * @param msg human readable error message 348 */ 349 static void 350 resume_abort_with_error (struct AbortContext *ac, 351 unsigned int http_status, 352 enum TALER_ErrorCode ec, 353 const char *msg) 354 { 355 resume_abort_with_response (ac, 356 http_status, 357 TALER_MHD_make_error (ec, 358 msg)); 359 } 360 361 362 /** 363 * Generate a response that indicates abortment success. 364 * 365 * @param ac abortment context 366 */ 367 static void 368 generate_success_response (struct AbortContext *ac) 369 { 370 json_t *refunds; 371 unsigned int hc = MHD_HTTP_OK; 372 373 refunds = json_array (); 374 if (NULL == refunds) 375 { 376 GNUNET_break (0); 377 resume_abort_with_error (ac, 378 MHD_HTTP_INTERNAL_SERVER_ERROR, 379 TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE, 380 "could not create JSON array"); 381 return; 382 } 383 for (size_t i = 0; i<ac->coins_cnt; i++) 384 { 385 struct RefundDetails *rdi = &ac->rd[i]; 386 json_t *detail; 387 388 if (rdi->found_deposit) 389 { 390 if ( ( (MHD_HTTP_BAD_REQUEST <= rdi->http_status) && 391 (MHD_HTTP_NOT_FOUND != rdi->http_status) && 392 (MHD_HTTP_GONE != rdi->http_status) ) || 393 (0 == rdi->http_status) || 394 (NULL == rdi->exchange_reply) ) 395 { 396 hc = MHD_HTTP_BAD_GATEWAY; 397 } 398 } 399 if (! rdi->found_deposit) 400 { 401 detail = GNUNET_JSON_PACK ( 402 GNUNET_JSON_pack_string ("type", 403 "undeposited")); 404 } 405 else 406 { 407 if (MHD_HTTP_OK != rdi->http_status) 408 { 409 detail = GNUNET_JSON_PACK ( 410 GNUNET_JSON_pack_string ("type", 411 "failure"), 412 GNUNET_JSON_pack_uint64 ("exchange_status", 413 rdi->http_status), 414 GNUNET_JSON_pack_uint64 ("exchange_code", 415 (NULL != rdi->exchange_reply) 416 ? TALER_JSON_get_error_code ( 417 rdi->exchange_reply) 418 : TALER_EC_GENERIC_INVALID_RESPONSE), 419 GNUNET_JSON_pack_allow_null ( 420 GNUNET_JSON_pack_object_incref ("exchange_reply", 421 rdi->exchange_reply))); 422 } 423 else 424 { 425 detail = GNUNET_JSON_PACK ( 426 GNUNET_JSON_pack_string ("type", 427 "success"), 428 GNUNET_JSON_pack_uint64 ("exchange_status", 429 rdi->http_status), 430 GNUNET_JSON_pack_data_auto ("exchange_sig", 431 &rdi->exchange_sig), 432 GNUNET_JSON_pack_data_auto ("exchange_pub", 433 &rdi->exchange_pub)); 434 } 435 } 436 GNUNET_assert (0 == 437 json_array_append_new (refunds, 438 detail)); 439 } 440 441 /* Resume and send back the response. */ 442 resume_abort_with_response ( 443 ac, 444 hc, 445 TALER_MHD_MAKE_JSON_PACK ( 446 GNUNET_JSON_pack_array_steal ("refunds", 447 refunds))); 448 } 449 450 451 /** 452 * Custom cleanup routine for a `struct AbortContext`. 453 * 454 * @param cls the `struct AbortContext` to clean up. 455 */ 456 static void 457 abort_context_cleanup (void *cls) 458 { 459 struct AbortContext *ac = cls; 460 461 if (NULL != ac->timeout_task) 462 { 463 GNUNET_SCHEDULER_cancel (ac->timeout_task); 464 ac->timeout_task = NULL; 465 } 466 abort_refunds (ac); 467 for (size_t i = 0; i<ac->coins_cnt; i++) 468 { 469 struct RefundDetails *rdi = &ac->rd[i]; 470 471 if (NULL != rdi->exchange_reply) 472 { 473 json_decref (rdi->exchange_reply); 474 rdi->exchange_reply = NULL; 475 } 476 GNUNET_free (rdi->exchange_url); 477 } 478 GNUNET_free (ac->rd); 479 if (NULL != ac->fo) 480 { 481 TMH_EXCHANGES_keys4exchange_cancel (ac->fo); 482 ac->fo = NULL; 483 } 484 if (NULL != ac->response) 485 { 486 MHD_destroy_response (ac->response); 487 ac->response = NULL; 488 } 489 GNUNET_CONTAINER_DLL_remove (ac_head, 490 ac_tail, 491 ac); 492 GNUNET_free (ac); 493 } 494 495 496 /** 497 * Find the exchange we need to talk to for the next 498 * pending deposit permission. 499 * 500 * @param ac abortment context we are processing 501 */ 502 static void 503 find_next_exchange (struct AbortContext *ac); 504 505 506 /** 507 * Function called with the result from the exchange (to be 508 * passed back to the wallet). 509 * 510 * @param cls closure 511 * @param rr response data 512 */ 513 static void 514 refund_cb (struct RefundDetails *rd, 515 const struct TALER_EXCHANGE_PostCoinsRefundResponse *rr) 516 { 517 const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr; 518 struct AbortContext *ac = rd->ac; 519 520 rd->rh = NULL; 521 rd->http_status = hr->http_status; 522 rd->exchange_reply = json_incref ((json_t*) hr->reply); 523 if (MHD_HTTP_OK == hr->http_status) 524 { 525 rd->exchange_pub = rr->details.ok.exchange_pub; 526 rd->exchange_sig = rr->details.ok.exchange_sig; 527 } 528 ac->pending_at_ce--; 529 if (0 == ac->pending_at_ce) 530 find_next_exchange (ac); 531 } 532 533 534 /** 535 * Function called with the result of our exchange lookup. 536 * 537 * @param cls the `struct AbortContext` 538 * @param keys keys of the exchange 539 * @param exchange representation of the exchange 540 */ 541 static void 542 process_abort_with_exchange (void *cls, 543 struct TALER_EXCHANGE_Keys *keys, 544 struct TMH_Exchange *exchange) 545 { 546 struct AbortContext *ac = cls; 547 548 (void) exchange; 549 ac->fo = NULL; 550 GNUNET_assert (GNUNET_YES == ac->suspended); 551 if (NULL == keys) 552 { 553 resume_abort_with_response ( 554 ac, 555 MHD_HTTP_GATEWAY_TIMEOUT, 556 TALER_MHD_make_error ( 557 TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT, 558 NULL)); 559 return; 560 } 561 /* Initiate refund operation for all coins of 562 the current exchange (!) */ 563 GNUNET_assert (0 == ac->pending_at_ce); 564 for (size_t i = 0; i<ac->coins_cnt; i++) 565 { 566 struct RefundDetails *rdi = &ac->rd[i]; 567 568 if (rdi->processed) 569 continue; 570 GNUNET_assert (NULL == rdi->rh); 571 if (0 != strcmp (rdi->exchange_url, 572 ac->current_exchange)) 573 continue; 574 rdi->processed = true; 575 ac->pending--; 576 if (! rdi->found_deposit) 577 { 578 /* Coin wasn't even deposited yet, we do not need to refund it. */ 579 continue; 580 } 581 rdi->rh = TALER_EXCHANGE_post_coins_refund_create ( 582 TMH_curl_ctx, 583 ac->current_exchange, 584 keys, 585 &rdi->amount_with_fee, 586 &ac->h_contract_terms, 587 &rdi->coin_pub, 588 0, /* rtransaction_id */ 589 &ac->hc->instance->merchant_priv); 590 if (NULL == rdi->rh) 591 { 592 GNUNET_break_op (0); 593 resume_abort_with_error (ac, 594 MHD_HTTP_INTERNAL_SERVER_ERROR, 595 TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_EXCHANGE_REFUND_FAILED, 596 "Failed to start refund with exchange"); 597 return; 598 } 599 GNUNET_assert (TALER_EC_NONE == 600 TALER_EXCHANGE_post_coins_refund_start (rdi->rh, 601 &refund_cb, 602 rdi)); 603 ac->pending_at_ce++; 604 } 605 /* Still continue if no coins for this exchange were deposited. */ 606 if (0 == ac->pending_at_ce) 607 find_next_exchange (ac); 608 } 609 610 611 /** 612 * Begin of the DB transaction. If required (from 613 * soft/serialization errors), the transaction can be 614 * restarted here. 615 * 616 * @param ac abortment context to transact 617 */ 618 static void 619 begin_transaction (struct AbortContext *ac); 620 621 622 /** 623 * Find the exchange we need to talk to for the next 624 * pending deposit permission. 625 * 626 * @param ac abortment context we are processing 627 */ 628 static void 629 find_next_exchange (struct AbortContext *ac) 630 { 631 for (size_t i = 0; i<ac->coins_cnt; i++) 632 { 633 struct RefundDetails *rdi = &ac->rd[i]; 634 635 if (! rdi->processed) 636 { 637 ac->current_exchange = rdi->exchange_url; 638 ac->fo = TMH_EXCHANGES_keys4exchange (ac->current_exchange, 639 false, 640 &process_abort_with_exchange, 641 ac); 642 if (NULL == ac->fo) 643 { 644 /* strange, should have happened on pay! */ 645 GNUNET_break (0); 646 resume_abort_with_error (ac, 647 MHD_HTTP_INTERNAL_SERVER_ERROR, 648 TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED, 649 ac->current_exchange); 650 return; 651 } 652 return; 653 } 654 } 655 ac->current_exchange = NULL; 656 GNUNET_assert (0 == ac->pending); 657 /* We are done with all the HTTP requests, go back and try 658 the 'big' database transaction! (It should work now!) */ 659 begin_transaction (ac); 660 } 661 662 663 /** 664 * Function called with information about a coin that was deposited. 665 * Note that we must not roll back or restart the transaction here, as 666 * we are called from within the iteration over the deposits. Failures 667 * are merely recorded in @a ac->row_qs and then handled by 668 * #begin_transaction() once the iteration has completed. 669 * 670 * @param cls closure 671 * @param exchange_url exchange where @a coin_pub was deposited 672 * @param coin_pub public key of the coin 673 * @param amount_with_fee amount the exchange will deposit for this coin 674 * @param deposit_fee fee the exchange will charge for this coin 675 * @param refund_fee fee the exchange will charge for refunding this coin 676 * @param wire_fee fee to be paid for the wire transfer 677 */ 678 static void 679 refund_coins (void *cls, 680 const char *exchange_url, 681 const struct TALER_CoinSpendPublicKeyP *coin_pub, 682 const struct TALER_Amount *amount_with_fee, 683 const struct TALER_Amount *deposit_fee, 684 const struct TALER_Amount *refund_fee, 685 const struct TALER_Amount *wire_fee) 686 { 687 struct AbortContext *ac = cls; 688 struct GNUNET_TIME_Timestamp now; 689 690 (void) deposit_fee; 691 (void) refund_fee; 692 (void) wire_fee; 693 if (0 > ac->row_qs) 694 return; /* already failed, do not touch the DB any further */ 695 now = GNUNET_TIME_timestamp_get (); 696 for (size_t i = 0; i<ac->coins_cnt; i++) 697 { 698 struct RefundDetails *rdi = &ac->rd[i]; 699 enum GNUNET_DB_QueryStatus qs; 700 701 if ( (0 != 702 GNUNET_memcmp (coin_pub, 703 &rdi->coin_pub)) || 704 (0 != 705 strcmp (exchange_url, 706 rdi->exchange_url)) ) 707 continue; /* not in request */ 708 rdi->found_deposit = true; 709 rdi->amount_with_fee = *amount_with_fee; 710 /* Store refund in DB */ 711 qs = TALER_MERCHANTDB_refund_coin (TMH_db, 712 ac->hc->instance->settings.id, 713 &ac->h_contract_terms, 714 now, 715 coin_pub, 716 /* justification */ 717 "incomplete abortment aborted"); 718 if (0 > qs) 719 { 720 /* Always report on hard error to enable diagnostics */ 721 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 722 ac->row_qs = qs; 723 return; 724 } 725 } /* for all coins */ 726 } 727 728 729 /** 730 * Begin of the DB transaction. If required (from soft/serialization errors), 731 * the transaction can be restarted here. 732 * 733 * @param ac abortment context to transact 734 */ 735 static void 736 begin_transaction (struct AbortContext *ac) 737 { 738 enum GNUNET_DB_QueryStatus qs; 739 740 /* Avoid re-trying transactions on soft errors forever! */ 741 if (ac->retry_counter++ > MAX_RETRIES) 742 { 743 GNUNET_break (0); 744 resume_abort_with_error (ac, 745 MHD_HTTP_INTERNAL_SERVER_ERROR, 746 TALER_EC_GENERIC_DB_SOFT_FAILURE, 747 NULL); 748 return; 749 } 750 GNUNET_assert (GNUNET_YES == ac->suspended); 751 752 /* First, try to see if we have all we need already done */ 753 TALER_MERCHANTDB_preflight (TMH_db); 754 qs = TALER_MERCHANTDB_set_instance ( 755 TMH_db, 756 ac->hc->instance->settings.id); 757 if (0 >= qs) 758 { 759 GNUNET_break (0); 760 resume_abort_with_error (ac, 761 MHD_HTTP_INTERNAL_SERVER_ERROR, 762 TALER_EC_GENERIC_DB_START_FAILED, 763 "set_instance"); 764 goto cleanup; 765 } 766 if (GNUNET_OK != 767 TALER_MERCHANTDB_start (TMH_db, 768 "run abort")) 769 { 770 GNUNET_break (0); 771 resume_abort_with_error (ac, 772 MHD_HTTP_INTERNAL_SERVER_ERROR, 773 TALER_EC_GENERIC_DB_START_FAILED, 774 NULL); 775 goto cleanup; 776 } 777 778 /* check payment was indeed incomplete 779 (now that we are in the transaction scope!) */ 780 { 781 struct TALER_PrivateContractHashP h_contract_terms; 782 bool paid; 783 784 qs = TALER_MERCHANTDB_lookup_order_status (TMH_db, 785 ac->hc->instance->settings.id, 786 ac->hc->infix, 787 &h_contract_terms, 788 &paid); 789 switch (qs) 790 { 791 case GNUNET_DB_STATUS_SOFT_ERROR: 792 case GNUNET_DB_STATUS_HARD_ERROR: 793 /* Always report on hard error to enable diagnostics */ 794 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 795 TALER_MERCHANTDB_rollback (TMH_db); 796 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 797 { 798 begin_transaction (ac); 799 goto cleanup; 800 } 801 /* Always report on hard error as well to enable diagnostics */ 802 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 803 resume_abort_with_error (ac, 804 MHD_HTTP_INTERNAL_SERVER_ERROR, 805 TALER_EC_GENERIC_DB_FETCH_FAILED, 806 "order status"); 807 goto cleanup; 808 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 809 TALER_MERCHANTDB_rollback (TMH_db); 810 resume_abort_with_error (ac, 811 MHD_HTTP_NOT_FOUND, 812 TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND, 813 "Could not find contract"); 814 goto cleanup; 815 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 816 if (paid) 817 { 818 /* Payment is complete, refuse to abort. */ 819 TALER_MERCHANTDB_rollback (TMH_db); 820 resume_abort_with_error (ac, 821 MHD_HTTP_PRECONDITION_FAILED, 822 TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_REFUND_REFUSED_PAYMENT_COMPLETE, 823 "Payment was complete, refusing to abort"); 824 goto cleanup; 825 } 826 } 827 if (0 != 828 GNUNET_memcmp (&ac->h_contract_terms, 829 &h_contract_terms)) 830 { 831 GNUNET_break_op (0); 832 TALER_MERCHANTDB_rollback (TMH_db); 833 resume_abort_with_error (ac, 834 MHD_HTTP_FORBIDDEN, 835 TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_HASH_MISSMATCH, 836 "Provided hash does not match order on file"); 837 goto cleanup; 838 } 839 } 840 841 /* Mark all deposits we have in our database for the order as refunded. */ 842 ac->row_qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 843 qs = TALER_MERCHANTDB_lookup_deposits (TMH_db, 844 ac->hc->instance->settings.id, 845 &ac->h_contract_terms, 846 &refund_coins, 847 ac); 848 if (0 > qs) 849 { 850 TALER_MERCHANTDB_rollback (TMH_db); 851 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 852 { 853 begin_transaction (ac); 854 goto cleanup; 855 } 856 /* Always report on hard error as well to enable diagnostics */ 857 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 858 resume_abort_with_error (ac, 859 MHD_HTTP_INTERNAL_SERVER_ERROR, 860 TALER_EC_GENERIC_DB_FETCH_FAILED, 861 "deposits"); 862 goto cleanup; 863 } 864 /* Check if one of the refund_coin() operations done by the 865 #refund_coins() row callback failed; we must handle this here (and 866 not in the callback) as we may only roll back and restart the 867 transaction from outside of the row iteration. */ 868 if (0 > ac->row_qs) 869 { 870 TALER_MERCHANTDB_rollback (TMH_db); 871 if (GNUNET_DB_STATUS_SOFT_ERROR == ac->row_qs) 872 { 873 begin_transaction (ac); 874 goto cleanup; 875 } 876 /* Always report on hard error as well to enable diagnostics */ 877 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == ac->row_qs); 878 resume_abort_with_error (ac, 879 MHD_HTTP_INTERNAL_SERVER_ERROR, 880 TALER_EC_GENERIC_DB_STORE_FAILED, 881 "refund_coin"); 882 goto cleanup; 883 } 884 885 qs = TALER_MERCHANTDB_commit (TMH_db); 886 if (0 > qs) 887 { 888 TALER_MERCHANTDB_rollback (TMH_db); 889 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 890 { 891 begin_transaction (ac); 892 goto cleanup; 893 } 894 resume_abort_with_error (ac, 895 MHD_HTTP_INTERNAL_SERVER_ERROR, 896 TALER_EC_GENERIC_DB_COMMIT_FAILED, 897 NULL); 898 goto cleanup; 899 } 900 901 /* At this point, the refund got correctly committed 902 into the database. Tell exchange about abort/refund. */ 903 if (ac->pending > 0) 904 { 905 find_next_exchange (ac); 906 goto cleanup; 907 } 908 generate_success_response (ac); 909 cleanup: 910 GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 911 TALER_MERCHANTDB_set_instance ( 912 TMH_db, 913 NULL)); 914 } 915 916 917 /** 918 * Try to parse the abort request into the given abort context. 919 * Schedules an error response in the connection on failure. 920 * 921 * @param connection HTTP connection we are receiving abortment on 922 * @param hc context we use to handle the abortment 923 * @param ac state of the /abort call 924 * @return #GNUNET_OK on success, 925 * #GNUNET_NO on failure (response was queued with MHD) 926 * #GNUNET_SYSERR on hard error (MHD connection must be dropped) 927 */ 928 static enum GNUNET_GenericReturnValue 929 parse_abort (struct MHD_Connection *connection, 930 struct TMH_HandlerContext *hc, 931 struct AbortContext *ac) 932 { 933 const json_t *coins; 934 struct GNUNET_JSON_Specification spec[] = { 935 GNUNET_JSON_spec_array_const ("coins", 936 &coins), 937 GNUNET_JSON_spec_fixed_auto ("h_contract", 938 &ac->h_contract_terms), 939 940 GNUNET_JSON_spec_end () 941 }; 942 enum GNUNET_GenericReturnValue res; 943 944 res = TALER_MHD_parse_json_data (connection, 945 hc->request_body, 946 spec); 947 if (GNUNET_YES != res) 948 { 949 GNUNET_break_op (0); 950 return res; 951 } 952 ac->coins_cnt = json_array_size (coins); 953 if (0 == ac->coins_cnt) 954 { 955 GNUNET_break_op (0); 956 return (MHD_YES == 957 TALER_MHD_reply_with_error (connection, 958 MHD_HTTP_BAD_REQUEST, 959 TALER_EC_MERCHANT_POST_ORDERS_ID_ABORT_COINS_ARRAY_EMPTY, 960 "coins")) 961 ? GNUNET_NO 962 : GNUNET_SYSERR; 963 } 964 /* note: 1 coin = 1 deposit confirmation expected */ 965 ac->pending = ac->coins_cnt; 966 ac->rd = GNUNET_new_array (ac->coins_cnt, 967 struct RefundDetails); 968 /* This loop populates the array 'rd' in 'ac' */ 969 { 970 unsigned int coins_index; 971 json_t *coin; 972 json_array_foreach (coins, coins_index, coin) 973 { 974 struct RefundDetails *rd = &ac->rd[coins_index]; 975 const char *exchange_url; 976 struct GNUNET_JSON_Specification ispec[] = { 977 TALER_JSON_spec_web_url ("exchange_url", 978 &exchange_url), 979 GNUNET_JSON_spec_fixed_auto ("coin_pub", 980 &rd->coin_pub), 981 GNUNET_JSON_spec_end () 982 }; 983 984 res = TALER_MHD_parse_json_data (connection, 985 coin, 986 ispec); 987 if (GNUNET_YES != res) 988 { 989 GNUNET_break_op (0); 990 return res; 991 } 992 rd->exchange_url = GNUNET_strdup (exchange_url); 993 rd->index = coins_index; 994 rd->ac = ac; 995 } 996 } 997 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 998 "Handling /abort for order `%s' with contract hash `%s'\n", 999 ac->hc->infix, 1000 GNUNET_h2s (&ac->h_contract_terms.hash)); 1001 return GNUNET_OK; 1002 } 1003 1004 1005 /** 1006 * Handle a timeout for the processing of the abort request. 1007 * 1008 * @param cls our `struct AbortContext` 1009 */ 1010 static void 1011 handle_abort_timeout (void *cls) 1012 { 1013 struct AbortContext *ac = cls; 1014 1015 ac->timeout_task = NULL; 1016 GNUNET_assert (GNUNET_YES == ac->suspended); 1017 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1018 "Resuming abort with error after timeout\n"); 1019 if (NULL != ac->fo) 1020 { 1021 TMH_EXCHANGES_keys4exchange_cancel (ac->fo); 1022 ac->fo = NULL; 1023 } 1024 resume_abort_with_error (ac, 1025 MHD_HTTP_GATEWAY_TIMEOUT, 1026 TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT, 1027 NULL); 1028 } 1029 1030 1031 enum MHD_Result 1032 TMH_post_orders_ID_abort (const struct TMH_RequestHandler *rh, 1033 struct MHD_Connection *connection, 1034 struct TMH_HandlerContext *hc) 1035 { 1036 struct AbortContext *ac = hc->ctx; 1037 1038 if (NULL == ac) 1039 { 1040 ac = GNUNET_new (struct AbortContext); 1041 GNUNET_CONTAINER_DLL_insert (ac_head, 1042 ac_tail, 1043 ac); 1044 ac->connection = connection; 1045 ac->hc = hc; 1046 hc->ctx = ac; 1047 hc->cc = &abort_context_cleanup; 1048 } 1049 if (GNUNET_SYSERR == ac->suspended) 1050 return MHD_NO; /* during shutdown, we don't generate any more replies */ 1051 if (0 != ac->response_code) 1052 { 1053 enum MHD_Result res; 1054 1055 /* We are *done* processing the request, 1056 just queue the response (!) */ 1057 if (UINT_MAX == ac->response_code) 1058 { 1059 GNUNET_break (0); 1060 return MHD_NO; /* hard error */ 1061 } 1062 res = MHD_queue_response (connection, 1063 ac->response_code, 1064 ac->response); 1065 MHD_destroy_response (ac->response); 1066 ac->response = NULL; 1067 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1068 "Queueing response (%u) for /abort (%s).\n", 1069 (unsigned int) ac->response_code, 1070 res ? "OK" : "FAILED"); 1071 return res; 1072 } 1073 { 1074 enum GNUNET_GenericReturnValue ret; 1075 1076 ret = parse_abort (connection, 1077 hc, 1078 ac); 1079 if (GNUNET_OK != ret) 1080 return (GNUNET_NO == ret) 1081 ? MHD_YES 1082 : MHD_NO; 1083 } 1084 1085 /* Abort not finished, suspend while we interact with the exchange */ 1086 GNUNET_assert (GNUNET_NO == ac->suspended); 1087 MHD_suspend_connection (connection); 1088 ac->suspended = GNUNET_YES; 1089 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 1090 "Suspending abort handling while working with the exchange\n"); 1091 ac->timeout_task = GNUNET_SCHEDULER_add_delayed (ABORT_GENERIC_TIMEOUT, 1092 &handle_abort_timeout, 1093 ac); 1094 begin_transaction (ac); 1095 return MHD_YES; 1096 } 1097 1098 1099 /* end of taler-merchant-httpd_post-orders-ORDER_ID-abort.c */