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