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