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