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