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