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