taler-merchant-httpd_post-orders-ORDER_ID-refund.c (25850B)
1 /* 2 This file is part of TALER 3 (C) 2020-2022 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 /** 21 * @file src/backend/taler-merchant-httpd_post-orders-ORDER_ID-refund.c 22 * @brief handling of POST /orders/$ID/refund requests 23 * @author Jonathan Buchanan 24 */ 25 #include "platform.h" 26 struct CoinRefund; 27 #define TALER_EXCHANGE_POST_COINS_REFUND_RESULT_CLOSURE struct CoinRefund 28 #include <taler/taler_dbevents.h> 29 #include <taler/taler_signatures.h> 30 #include <taler/taler_json_lib.h> 31 #include <taler/taler_exchange_service.h> 32 #include "taler-merchant-httpd.h" 33 #include "taler-merchant-httpd_exchanges.h" 34 #include "taler-merchant-httpd_get-exchanges.h" 35 #include "taler-merchant-httpd_post-orders-ORDER_ID-refund.h" 36 #include "merchant-database/insert_refund_proof.h" 37 #include "merchant-database/lookup_contract_terms.h" 38 #include "merchant-database/set_instance.h" 39 #include "merchant-database/lookup_refund_proof.h" 40 #include "merchant-database/lookup_refunds_detailed.h" 41 #include "merchant-database/event_notify.h" 42 #include "merchant-database/preflight.h" 43 44 45 /** 46 * Information we keep for each coin to be refunded. 47 */ 48 struct CoinRefund 49 { 50 51 /** 52 * Kept in a DLL. 53 */ 54 struct CoinRefund *next; 55 56 /** 57 * Kept in a DLL. 58 */ 59 struct CoinRefund *prev; 60 61 /** 62 * Request to connect to the target exchange. 63 */ 64 struct TMH_EXCHANGES_KeysOperation *fo; 65 66 /** 67 * Handle for the refund operation with the exchange. 68 */ 69 struct TALER_EXCHANGE_PostCoinsRefundHandle *rh; 70 71 /** 72 * Request this operation is part of. 73 */ 74 struct PostRefundData *prd; 75 76 /** 77 * URL of the exchange for this @e coin_pub. 78 */ 79 char *exchange_url; 80 81 /** 82 * Fully reply from the exchange, only possibly set if 83 * we got a JSON reply and a non-#MHD_HTTP_OK error code 84 */ 85 json_t *exchange_reply; 86 87 /** 88 * When did the merchant grant the refund. To be used to group events 89 * in the wallet. 90 */ 91 struct GNUNET_TIME_Timestamp execution_time; 92 93 /** 94 * Coin to refund. 95 */ 96 struct TALER_CoinSpendPublicKeyP coin_pub; 97 98 /** 99 * Refund transaction ID to use. 100 */ 101 uint64_t rtransaction_id; 102 103 /** 104 * Unique serial number identifying the refund. 105 */ 106 uint64_t refund_serial; 107 108 /** 109 * Amount to refund. 110 */ 111 struct TALER_Amount refund_amount; 112 113 /** 114 * Public key of the exchange affirming the refund. 115 */ 116 struct TALER_ExchangePublicKeyP exchange_pub; 117 118 /** 119 * Signature of the exchange affirming the refund. 120 */ 121 struct TALER_ExchangeSignatureP exchange_sig; 122 123 /** 124 * HTTP status from the exchange, #MHD_HTTP_OK if 125 * @a exchange_pub and @a exchange_sig are valid. 126 */ 127 unsigned int exchange_status; 128 129 /** 130 * HTTP error code from the exchange. 131 */ 132 enum TALER_ErrorCode exchange_code; 133 134 }; 135 136 137 /** 138 * Context for the operation. 139 */ 140 struct PostRefundData 141 { 142 143 /** 144 * Hashed version of contract terms. All zeros if not provided. 145 */ 146 struct TALER_PrivateContractHashP h_contract_terms; 147 148 /** 149 * DLL of (suspended) requests. 150 */ 151 struct PostRefundData *next; 152 153 /** 154 * DLL of (suspended) requests. 155 */ 156 struct PostRefundData *prev; 157 158 /** 159 * Refunds for this order. Head of DLL. 160 */ 161 struct CoinRefund *cr_head; 162 163 /** 164 * Refunds for this order. Tail of DLL. 165 */ 166 struct CoinRefund *cr_tail; 167 168 /** 169 * Context of the request. 170 */ 171 struct TMH_HandlerContext *hc; 172 173 /** 174 * Entry in the #resume_timeout_heap for this check payment, if we are 175 * suspended. 176 */ 177 struct TMH_SuspendedConnection sc; 178 179 /** 180 * order ID for the payment 181 */ 182 const char *order_id; 183 184 /** 185 * Where to get the contract 186 */ 187 const char *contract_url; 188 189 /** 190 * fulfillment URL of the contract (valid as long as 191 * @e contract_terms is valid). 192 */ 193 const char *fulfillment_url; 194 195 /** 196 * session of the client 197 */ 198 const char *session_id; 199 200 /** 201 * Contract terms of the payment we are checking. NULL when they 202 * are not (yet) known. 203 */ 204 json_t *contract_terms; 205 206 /** 207 * Total refunds granted for this payment. Only initialized 208 * if @e refunded is set to true. 209 */ 210 struct TALER_Amount refund_amount; 211 212 /** 213 * Did we suspend @a connection and are thus in 214 * the #prd_head DLL (#GNUNET_YES). Set to 215 * #GNUNET_NO if we are not suspended, and to 216 * #GNUNET_SYSERR if we should close the connection 217 * without a response due to shutdown. 218 */ 219 enum GNUNET_GenericReturnValue suspended; 220 221 /** 222 * Return code: #TALER_EC_NONE if successful. 223 */ 224 enum TALER_ErrorCode ec; 225 226 /** 227 * HTTP status to use for the reply, 0 if not yet known. 228 */ 229 unsigned int http_status; 230 231 /** 232 * Set to true if we are dealing with an unclaimed order 233 * (and thus @e h_contract_terms is not set, and certain 234 * DB queries will not work). 235 */ 236 bool unclaimed; 237 238 /** 239 * Set to true if this payment has been refunded and 240 * @e refund_amount is initialized. 241 */ 242 bool refunded; 243 244 /** 245 * Set to true if a refund is still available for the 246 * wallet for this payment. 247 */ 248 bool refund_available; 249 250 /** 251 * Set to true if the client requested HTML, otherwise 252 * we generate JSON. 253 */ 254 bool generate_html; 255 256 }; 257 258 259 /** 260 * Head of DLL of (suspended) requests. 261 */ 262 static struct PostRefundData *prd_head; 263 264 /** 265 * Tail of DLL of (suspended) requests. 266 */ 267 static struct PostRefundData *prd_tail; 268 269 270 /** 271 * Function called when we are done processing a refund request. 272 * Frees memory associated with @a ctx. 273 * 274 * @param ctx a `struct PostRefundData` 275 */ 276 static void 277 refund_cleanup (void *ctx) 278 { 279 struct PostRefundData *prd = ctx; 280 struct CoinRefund *cr; 281 282 while (NULL != (cr = prd->cr_head)) 283 { 284 GNUNET_CONTAINER_DLL_remove (prd->cr_head, 285 prd->cr_tail, 286 cr); 287 json_decref (cr->exchange_reply); 288 GNUNET_free (cr->exchange_url); 289 if (NULL != cr->fo) 290 { 291 TMH_EXCHANGES_keys4exchange_cancel (cr->fo); 292 cr->fo = NULL; 293 } 294 if (NULL != cr->rh) 295 { 296 TALER_EXCHANGE_post_coins_refund_cancel (cr->rh); 297 cr->rh = NULL; 298 } 299 GNUNET_free (cr); 300 } 301 json_decref (prd->contract_terms); 302 GNUNET_free (prd); 303 } 304 305 306 /** 307 * Force resuming all suspended order lookups, needed during shutdown. 308 */ 309 void 310 TMH_force_wallet_refund_order_resume (void) 311 { 312 struct PostRefundData *prd; 313 314 while (NULL != (prd = prd_head)) 315 { 316 GNUNET_CONTAINER_DLL_remove (prd_head, 317 prd_tail, 318 prd); 319 GNUNET_assert (GNUNET_YES == prd->suspended); 320 prd->suspended = GNUNET_SYSERR; 321 MHD_resume_connection (prd->sc.con); 322 } 323 } 324 325 326 /** 327 * Check if @a prd has exchange requests still pending. 328 * 329 * @param prd state to check 330 * @return true if activities are still pending 331 */ 332 static bool 333 exchange_operations_pending (struct PostRefundData *prd) 334 { 335 for (struct CoinRefund *cr = prd->cr_head; 336 NULL != cr; 337 cr = cr->next) 338 { 339 if ( (NULL != cr->fo) || 340 (NULL != cr->rh) ) 341 return true; 342 } 343 return false; 344 } 345 346 347 /** 348 * Check if @a prd is ready to be resumed, and if so, do it. 349 * 350 * @param prd refund request to be possibly ready 351 */ 352 static void 353 check_resume_prd (struct PostRefundData *prd) 354 { 355 if ( (TALER_EC_NONE == prd->ec) && 356 exchange_operations_pending (prd) ) 357 return; 358 GNUNET_CONTAINER_DLL_remove (prd_head, 359 prd_tail, 360 prd); 361 GNUNET_assert (prd->suspended); 362 prd->suspended = GNUNET_NO; 363 MHD_resume_connection (prd->sc.con); 364 TALER_MHD_daemon_trigger (); 365 } 366 367 368 /** 369 * Notify applications waiting for a client to obtain 370 * a refund. 371 * 372 * @param prd refund request with the change 373 */ 374 static void 375 notify_refund_obtained (struct PostRefundData *prd) 376 { 377 struct TMH_OrderPayEventP refund_eh = { 378 .header.size = htons (sizeof (refund_eh)), 379 .header.type = htons (TALER_DBEVENT_MERCHANT_REFUND_OBTAINED), 380 .merchant_pub = prd->hc->instance->merchant_pub 381 }; 382 383 GNUNET_CRYPTO_hash (prd->order_id, 384 strlen (prd->order_id), 385 &refund_eh.h_order_id); 386 TALER_MERCHANTDB_event_notify (TMH_db, 387 &refund_eh.header, 388 NULL, 389 0); 390 } 391 392 393 /** 394 * Callbacks of this type are used to serve the result of submitting a 395 * refund request to an exchange. 396 * 397 * @param cls a `struct CoinRefund` 398 * @param rr response data 399 */ 400 static void 401 refund_cb (struct CoinRefund *cr, 402 const struct TALER_EXCHANGE_PostCoinsRefundResponse *rr) 403 { 404 const struct TALER_EXCHANGE_HttpResponse *hr = &rr->hr; 405 406 cr->rh = NULL; 407 cr->exchange_status = hr->http_status; 408 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 409 "Exchange refund status for coin %s is %u\n", 410 TALER_B2S (&cr->coin_pub), 411 hr->http_status); 412 switch (hr->http_status) 413 { 414 case MHD_HTTP_OK: 415 { 416 enum GNUNET_DB_QueryStatus qs; 417 418 qs = TALER_MERCHANTDB_set_instance ( 419 TMH_db, 420 cr->prd->hc->instance->settings.id); 421 if (0 >= qs) 422 { 423 /* generally, this is relatively harmless for the merchant, but let's at 424 least log this. */ 425 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 426 "Failed to persist exchange response to /refund in database: %d\n", 427 qs); 428 } 429 else 430 { 431 cr->exchange_pub = rr->details.ok.exchange_pub; 432 cr->exchange_sig = rr->details.ok.exchange_sig; 433 qs = TALER_MERCHANTDB_insert_refund_proof (TMH_db, 434 cr->refund_serial, 435 &rr->details.ok.exchange_sig, 436 &rr->details.ok.exchange_pub); 437 if (0 >= qs) 438 { 439 /* generally, this is relatively harmless for the merchant, but let's at 440 least log this. */ 441 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 442 "Failed to persist exchange response to /refund in database: %d\n", 443 qs); 444 } 445 else 446 { 447 notify_refund_obtained (cr->prd); 448 } 449 GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 450 TALER_MERCHANTDB_set_instance ( 451 TMH_db, 452 NULL)); 453 } 454 } 455 break; 456 default: 457 cr->exchange_code = hr->ec; 458 cr->exchange_reply = json_incref ((json_t*) hr->reply); 459 break; 460 } 461 check_resume_prd (cr->prd); 462 } 463 464 465 /** 466 * Function called with the result of a 467 * #TMH_EXCHANGES_keys4exchange() 468 * operation. 469 * 470 * @param cls a `struct CoinRefund *` 471 * @param keys keys of exchange, NULL on error 472 * @param exchange representation of the exchange 473 */ 474 static void 475 exchange_found_cb (void *cls, 476 struct TALER_EXCHANGE_Keys *keys, 477 struct TMH_Exchange *exchange) 478 { 479 struct CoinRefund *cr = cls; 480 struct PostRefundData *prd = cr->prd; 481 482 (void) exchange; 483 cr->fo = NULL; 484 if (NULL == keys) 485 { 486 prd->http_status = MHD_HTTP_GATEWAY_TIMEOUT; 487 prd->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT; 488 check_resume_prd (prd); 489 return; 490 } 491 cr->rh = TALER_EXCHANGE_post_coins_refund_create ( 492 TMH_curl_ctx, 493 cr->exchange_url, 494 keys, 495 &cr->refund_amount, 496 &prd->h_contract_terms, 497 &cr->coin_pub, 498 cr->rtransaction_id, 499 &prd->hc->instance->merchant_priv); 500 GNUNET_assert (NULL != cr->rh); 501 GNUNET_assert (TALER_EC_NONE == 502 TALER_EXCHANGE_post_coins_refund_start (cr->rh, 503 &refund_cb, 504 cr)); 505 } 506 507 508 /** 509 * Function called with information about a refund. 510 * It is responsible for summing up the refund amount. 511 * 512 * @param cls closure 513 * @param refund_serial unique serial number of the refund 514 * @param timestamp time of the refund (for grouping of refunds in the wallet UI) 515 * @param coin_pub public coin from which the refund comes from 516 * @param exchange_url URL of the exchange that issued @a coin_pub 517 * @param rtransaction_id identificator of the refund 518 * @param reason human-readable explanation of the refund 519 * @param refund_amount refund amount which is being taken from @a coin_pub 520 * @param pending true if the this refund was not yet processed by the wallet/exchange 521 */ 522 static void 523 process_refunds_cb (void *cls, 524 uint64_t refund_serial, 525 struct GNUNET_TIME_Timestamp timestamp, 526 const struct TALER_CoinSpendPublicKeyP *coin_pub, 527 const char *exchange_url, 528 uint64_t rtransaction_id, 529 const char *reason, 530 const struct TALER_Amount *refund_amount, 531 bool pending) 532 { 533 struct PostRefundData *prd = cls; 534 struct CoinRefund *cr; 535 536 for (cr = prd->cr_head; 537 NULL != cr; 538 cr = cr->next) 539 if (cr->refund_serial == refund_serial) 540 return; 541 /* already known */ 542 543 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 544 "Found refund of %s for coin %s with reason `%s' in database\n", 545 TALER_amount2s (refund_amount), 546 TALER_B2S (coin_pub), 547 reason); 548 cr = GNUNET_new (struct CoinRefund); 549 cr->refund_serial = refund_serial; 550 cr->exchange_url = GNUNET_strdup (exchange_url); 551 cr->prd = prd; 552 cr->coin_pub = *coin_pub; 553 cr->rtransaction_id = rtransaction_id; 554 cr->refund_amount = *refund_amount; 555 cr->execution_time = timestamp; 556 GNUNET_CONTAINER_DLL_insert (prd->cr_head, 557 prd->cr_tail, 558 cr); 559 if (prd->refunded) 560 { 561 GNUNET_assert (0 <= 562 TALER_amount_add (&prd->refund_amount, 563 &prd->refund_amount, 564 refund_amount)); 565 return; 566 } 567 prd->refund_amount = *refund_amount; 568 prd->refunded = true; 569 prd->refund_available |= pending; 570 } 571 572 573 /** 574 * Obtain refunds for an order. 575 * 576 * @param rh context of the handler 577 * @param connection the MHD connection to handle 578 * @param[in,out] hc context with further information about the request 579 * @return MHD result code 580 */ 581 enum MHD_Result 582 TMH_post_orders_ID_refund (const struct TMH_RequestHandler *rh, 583 struct MHD_Connection *connection, 584 struct TMH_HandlerContext *hc) 585 { 586 struct PostRefundData *prd = hc->ctx; 587 enum GNUNET_DB_QueryStatus qs; 588 589 if (NULL == prd) 590 { 591 prd = GNUNET_new (struct PostRefundData); 592 prd->sc.con = connection; 593 prd->hc = hc; 594 prd->order_id = hc->infix; 595 hc->ctx = prd; 596 hc->cc = &refund_cleanup; 597 { 598 enum GNUNET_GenericReturnValue res; 599 600 struct GNUNET_JSON_Specification spec[] = { 601 GNUNET_JSON_spec_fixed_auto ("h_contract", 602 &prd->h_contract_terms), 603 GNUNET_JSON_spec_end () 604 }; 605 res = TALER_MHD_parse_json_data (connection, 606 hc->request_body, 607 spec); 608 if (GNUNET_OK != res) 609 return (GNUNET_NO == res) 610 ? MHD_YES 611 : MHD_NO; 612 } 613 614 TALER_MERCHANTDB_preflight (TMH_db); 615 { 616 json_t *contract_terms; 617 uint64_t order_serial; 618 619 qs = TALER_MERCHANTDB_lookup_contract_terms (TMH_db, 620 hc->instance->settings.id, 621 hc->infix, 622 &contract_terms, 623 &order_serial, 624 NULL); 625 if (0 > qs) 626 { 627 /* single, read-only SQL statements should never cause 628 serialization problems */ 629 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); 630 /* Always report on hard error as well to enable diagnostics */ 631 GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs); 632 return TALER_MHD_reply_with_error (connection, 633 MHD_HTTP_INTERNAL_SERVER_ERROR, 634 TALER_EC_GENERIC_DB_FETCH_FAILED, 635 "contract terms"); 636 } 637 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 638 { 639 json_decref (contract_terms); 640 return TALER_MHD_reply_with_error (connection, 641 MHD_HTTP_NOT_FOUND, 642 TALER_EC_MERCHANT_GENERIC_ORDER_UNKNOWN, 643 hc->infix); 644 } 645 { 646 struct TALER_PrivateContractHashP h_contract_terms; 647 648 if (GNUNET_OK != 649 TALER_JSON_contract_hash (contract_terms, 650 &h_contract_terms)) 651 { 652 GNUNET_break (0); 653 json_decref (contract_terms); 654 return TALER_MHD_reply_with_error (connection, 655 MHD_HTTP_INTERNAL_SERVER_ERROR, 656 TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH, 657 NULL); 658 } 659 json_decref (contract_terms); 660 if (0 != GNUNET_memcmp (&h_contract_terms, 661 &prd->h_contract_terms)) 662 { 663 return TALER_MHD_reply_with_error ( 664 connection, 665 MHD_HTTP_FORBIDDEN, 666 TALER_EC_MERCHANT_GENERIC_CONTRACT_HASH_DOES_NOT_MATCH_ORDER, 667 NULL); 668 } 669 } 670 } 671 } 672 if (GNUNET_SYSERR == prd->suspended) 673 return MHD_NO; /* we are in shutdown */ 674 675 if (TALER_EC_NONE != prd->ec) 676 { 677 GNUNET_break (0 != prd->http_status); 678 /* kill pending coin refund operations immediately, just to be 679 extra sure they don't modify 'prd' after we already created 680 a reply (this might not be needed, but feels safer). */ 681 for (struct CoinRefund *cr = prd->cr_head; 682 NULL != cr; 683 cr = cr->next) 684 { 685 if (NULL != cr->fo) 686 { 687 TMH_EXCHANGES_keys4exchange_cancel (cr->fo); 688 cr->fo = NULL; 689 } 690 if (NULL != cr->rh) 691 { 692 TALER_EXCHANGE_post_coins_refund_cancel (cr->rh); 693 cr->rh = NULL; 694 } 695 } 696 return TALER_MHD_reply_with_error (connection, 697 prd->http_status, 698 prd->ec, 699 NULL); 700 } 701 702 qs = TALER_MERCHANTDB_lookup_refunds_detailed (TMH_db, 703 hc->instance->settings.id, 704 &prd->h_contract_terms, 705 &process_refunds_cb, 706 prd); 707 if (0 > qs) 708 { 709 GNUNET_break (0); 710 return TALER_MHD_reply_with_error (connection, 711 MHD_HTTP_INTERNAL_SERVER_ERROR, 712 TALER_EC_GENERIC_DB_FETCH_FAILED, 713 "detailed refunds"); 714 } 715 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 716 { 717 /* We have a contract, which means the order was paid, but there 718 are no coins (maybe paid with a token?), and thus we cannot 719 do any refunds */ 720 return TALER_MHD_reply_static (connection, 721 MHD_HTTP_NO_CONTENT, 722 NULL, 723 NULL, 724 0); 725 } 726 727 /* Now launch exchange interactions, unless we already have the 728 response in the database! */ 729 for (struct CoinRefund *cr = prd->cr_head; 730 NULL != cr; 731 cr = cr->next) 732 { 733 qs = TALER_MERCHANTDB_lookup_refund_proof (TMH_db, 734 cr->refund_serial, 735 &cr->exchange_sig, 736 &cr->exchange_pub); 737 switch (qs) 738 { 739 case GNUNET_DB_STATUS_HARD_ERROR: 740 case GNUNET_DB_STATUS_SOFT_ERROR: 741 return TALER_MHD_reply_with_error (connection, 742 MHD_HTTP_INTERNAL_SERVER_ERROR, 743 TALER_EC_GENERIC_DB_FETCH_FAILED, 744 "refund proof"); 745 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 746 if (NULL == cr->exchange_reply) 747 { 748 /* We need to talk to the exchange */ 749 cr->fo = TMH_EXCHANGES_keys4exchange (cr->exchange_url, 750 false, 751 &exchange_found_cb, 752 cr); 753 if (NULL == cr->fo) 754 { 755 GNUNET_break (0); 756 return TALER_MHD_reply_with_error (connection, 757 MHD_HTTP_INTERNAL_SERVER_ERROR, 758 TALER_EC_MERCHANT_GENERIC_EXCHANGE_UNTRUSTED, 759 cr->exchange_url); 760 } 761 } 762 break; 763 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 764 /* We got a reply earlier, set status accordingly */ 765 cr->exchange_status = MHD_HTTP_OK; 766 break; 767 } 768 } 769 770 /* Check if there are still exchange operations pending */ 771 if (exchange_operations_pending (prd)) 772 { 773 if (GNUNET_NO == prd->suspended) 774 { 775 prd->suspended = GNUNET_YES; 776 MHD_suspend_connection (connection); 777 GNUNET_CONTAINER_DLL_insert (prd_head, 778 prd_tail, 779 prd); 780 } 781 return MHD_YES; /* we're still talking to the exchange */ 782 } 783 784 { 785 json_t *ra; 786 787 ra = json_array (); 788 GNUNET_assert (NULL != ra); 789 for (struct CoinRefund *cr = prd->cr_head; 790 NULL != cr; 791 cr = cr->next) 792 { 793 json_t *refund; 794 795 if (MHD_HTTP_OK != cr->exchange_status) 796 { 797 if (NULL == cr->exchange_reply) 798 { 799 refund = GNUNET_JSON_PACK ( 800 GNUNET_JSON_pack_string ("type", 801 "failure"), 802 GNUNET_JSON_pack_uint64 ("exchange_status", 803 cr->exchange_status), 804 GNUNET_JSON_pack_uint64 ("rtransaction_id", 805 cr->rtransaction_id), 806 GNUNET_JSON_pack_data_auto ("coin_pub", 807 &cr->coin_pub), 808 TALER_JSON_pack_amount ("refund_amount", 809 &cr->refund_amount), 810 GNUNET_JSON_pack_timestamp ("execution_time", 811 cr->execution_time)); 812 } 813 else 814 { 815 refund = GNUNET_JSON_PACK ( 816 GNUNET_JSON_pack_string ("type", 817 "failure"), 818 GNUNET_JSON_pack_uint64 ("exchange_status", 819 cr->exchange_status), 820 GNUNET_JSON_pack_uint64 ("exchange_code", 821 cr->exchange_code), 822 GNUNET_JSON_pack_object_incref ("exchange_reply", 823 cr->exchange_reply), 824 GNUNET_JSON_pack_uint64 ("rtransaction_id", 825 cr->rtransaction_id), 826 GNUNET_JSON_pack_data_auto ("coin_pub", 827 &cr->coin_pub), 828 TALER_JSON_pack_amount ("refund_amount", 829 &cr->refund_amount), 830 GNUNET_JSON_pack_timestamp ("execution_time", 831 cr->execution_time)); 832 } 833 } 834 else 835 { 836 refund = GNUNET_JSON_PACK ( 837 GNUNET_JSON_pack_string ("type", 838 "success"), 839 GNUNET_JSON_pack_uint64 ("exchange_status", 840 cr->exchange_status), 841 GNUNET_JSON_pack_data_auto ("exchange_sig", 842 &cr->exchange_sig), 843 GNUNET_JSON_pack_data_auto ("exchange_pub", 844 &cr->exchange_pub), 845 GNUNET_JSON_pack_uint64 ("rtransaction_id", 846 cr->rtransaction_id), 847 GNUNET_JSON_pack_data_auto ("coin_pub", 848 &cr->coin_pub), 849 TALER_JSON_pack_amount ("refund_amount", 850 &cr->refund_amount), 851 GNUNET_JSON_pack_timestamp ("execution_time", 852 cr->execution_time)); 853 } 854 GNUNET_assert ( 855 0 == 856 json_array_append_new (ra, 857 refund)); 858 } 859 860 return TALER_MHD_REPLY_JSON_PACK ( 861 connection, 862 MHD_HTTP_OK, 863 TALER_JSON_pack_amount ("refund_amount", 864 &prd->refund_amount), 865 GNUNET_JSON_pack_array_steal ("refunds", 866 ra), 867 GNUNET_JSON_pack_data_auto ("merchant_pub", 868 &hc->instance->merchant_pub)); 869 } 870 871 return MHD_YES; 872 } 873 874 875 /* end of taler-merchant-httpd_post-orders-ORDER_ID-refund.c */