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