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