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