taler-merchant-httpd_private-get-orders.c (31849B)
1 /* 2 This file is part of TALER 3 (C) 2019--2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-merchant-httpd_private-get-orders.c 18 * @brief implement GET /orders 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include "taler-merchant-httpd_private-get-orders.h" 23 #include <taler_merchant_util.h> 24 #include <taler/taler_json_lib.h> 25 #include <taler/taler_dbevents.h> 26 27 28 /** 29 * Sensible bound on TALER_MERCHANTDB_OrderFilter.delta 30 */ 31 #define MAX_DELTA 1024 32 33 34 /** 35 * A pending GET /orders request. 36 */ 37 struct TMH_PendingOrder 38 { 39 40 /** 41 * Kept in a DLL. 42 */ 43 struct TMH_PendingOrder *prev; 44 45 /** 46 * Kept in a DLL. 47 */ 48 struct TMH_PendingOrder *next; 49 50 /** 51 * Which connection was suspended. 52 */ 53 struct MHD_Connection *con; 54 55 /** 56 * Associated heap node. 57 */ 58 struct GNUNET_CONTAINER_HeapNode *hn; 59 60 /** 61 * Which instance is this client polling? This also defines 62 * which DLL this struct is part of. 63 */ 64 struct TMH_MerchantInstance *mi; 65 66 /** 67 * At what time does this request expire? If set in the future, we 68 * may wait this long for a payment to arrive before responding. 69 */ 70 struct GNUNET_TIME_Absolute long_poll_timeout; 71 72 /** 73 * Filter to apply. 74 */ 75 struct TALER_MERCHANTDB_OrderFilter of; 76 77 /** 78 * The array of orders. 79 */ 80 json_t *pa; 81 82 /** 83 * The name of the instance we are querying for. 84 */ 85 const char *instance_id; 86 87 /** 88 * Alias of @a of.summary_filter, but with memory to be released (owner). 89 */ 90 char *summary_filter; 91 92 /** 93 * The result after adding the orders (#TALER_EC_NONE for okay, anything else for an error). 94 */ 95 enum TALER_ErrorCode result; 96 97 /** 98 * Is the structure in the DLL 99 */ 100 bool in_dll; 101 }; 102 103 104 /** 105 * Task to timeout pending orders. 106 */ 107 static struct GNUNET_SCHEDULER_Task *order_timeout_task; 108 109 /** 110 * Heap for orders in long polling awaiting timeout. 111 */ 112 static struct GNUNET_CONTAINER_Heap *order_timeout_heap; 113 114 115 /** 116 * We are shutting down (or an instance is being deleted), force resume of all 117 * GET /orders requests. 118 * 119 * @param mi instance to force resuming for 120 */ 121 void 122 TMH_force_get_orders_resume (struct TMH_MerchantInstance *mi) 123 { 124 struct TMH_PendingOrder *po; 125 126 while (NULL != (po = mi->po_head)) 127 { 128 GNUNET_assert (po->in_dll); 129 GNUNET_CONTAINER_DLL_remove (mi->po_head, 130 mi->po_tail, 131 po); 132 GNUNET_assert (po == 133 GNUNET_CONTAINER_heap_remove_root (order_timeout_heap)); 134 MHD_resume_connection (po->con); 135 po->in_dll = false; 136 } 137 if (NULL != mi->po_eh) 138 { 139 TMH_db->event_listen_cancel (mi->po_eh); 140 mi->po_eh = NULL; 141 } 142 if (NULL != order_timeout_task) 143 { 144 GNUNET_SCHEDULER_cancel (order_timeout_task); 145 order_timeout_task = NULL; 146 } 147 if (NULL != order_timeout_heap) 148 { 149 GNUNET_CONTAINER_heap_destroy (order_timeout_heap); 150 order_timeout_heap = NULL; 151 } 152 } 153 154 155 /** 156 * Task run to trigger timeouts on GET /orders requests with long polling. 157 * 158 * @param cls unused 159 */ 160 static void 161 order_timeout (void *cls) 162 { 163 struct TMH_PendingOrder *po; 164 struct TMH_MerchantInstance *mi; 165 166 (void) cls; 167 order_timeout_task = NULL; 168 while (1) 169 { 170 po = GNUNET_CONTAINER_heap_peek (order_timeout_heap); 171 if (NULL == po) 172 { 173 /* release data structure, we don't need it right now */ 174 GNUNET_CONTAINER_heap_destroy (order_timeout_heap); 175 order_timeout_heap = NULL; 176 return; 177 } 178 if (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) 179 break; 180 GNUNET_assert (po == 181 GNUNET_CONTAINER_heap_remove_root (order_timeout_heap)); 182 po->hn = NULL; 183 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 184 "Resuming long polled job due to timeout\n"); 185 mi = po->mi; 186 GNUNET_assert (po->in_dll); 187 GNUNET_CONTAINER_DLL_remove (mi->po_head, 188 mi->po_tail, 189 po); 190 if ( (NULL == mi->po_head) && 191 (NULL != mi->po_eh) ) 192 { 193 TMH_db->event_listen_cancel (mi->po_eh); 194 mi->po_eh = NULL; 195 } 196 po->in_dll = false; 197 MHD_resume_connection (po->con); 198 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 199 } 200 order_timeout_task = GNUNET_SCHEDULER_add_at (po->long_poll_timeout, 201 &order_timeout, 202 NULL); 203 } 204 205 206 /** 207 * Cleanup our "context", where we stored the JSON array 208 * we are building for the response. 209 * 210 * @param ctx context to clean up, must be a `struct AddOrderState *` 211 */ 212 static void 213 cleanup (void *ctx) 214 { 215 struct TMH_PendingOrder *po = ctx; 216 217 if (po->in_dll) 218 { 219 struct TMH_MerchantInstance *mi = po->mi; 220 221 GNUNET_CONTAINER_DLL_remove (mi->po_head, 222 mi->po_tail, 223 po); 224 } 225 if (NULL != po->hn) 226 GNUNET_assert (po == 227 GNUNET_CONTAINER_heap_remove_node (po->hn)); 228 json_decref (po->pa); 229 GNUNET_free (po->summary_filter); 230 GNUNET_free (po); 231 } 232 233 234 /** 235 * Closure for #process_refunds_cb(). 236 */ 237 struct ProcessRefundsClosure 238 { 239 /** 240 * Place where we accumulate the granted refunds. 241 */ 242 struct TALER_Amount total_refund_amount; 243 244 /** 245 * Place where we accumulate the pending refunds. 246 */ 247 struct TALER_Amount pending_refund_amount; 248 249 /** 250 * Set to an error code if something goes wrong. 251 */ 252 enum TALER_ErrorCode ec; 253 }; 254 255 256 /** 257 * Function called with information about a refund. 258 * It is responsible for summing up the refund amount. 259 * 260 * @param cls closure 261 * @param refund_serial unique serial number of the refund 262 * @param timestamp time of the refund (for grouping of refunds in the wallet UI) 263 * @param coin_pub public coin from which the refund comes from 264 * @param exchange_url URL of the exchange that issued @a coin_pub 265 * @param rtransaction_id identificator of the refund 266 * @param reason human-readable explanation of the refund 267 * @param refund_amount refund amount which is being taken from @a coin_pub 268 * @param pending true if the this refund was not yet processed by the wallet/exchange 269 */ 270 static void 271 process_refunds_cb (void *cls, 272 uint64_t refund_serial, 273 struct GNUNET_TIME_Timestamp timestamp, 274 const struct TALER_CoinSpendPublicKeyP *coin_pub, 275 const char *exchange_url, 276 uint64_t rtransaction_id, 277 const char *reason, 278 const struct TALER_Amount *refund_amount, 279 bool pending) 280 { 281 struct ProcessRefundsClosure *prc = cls; 282 283 if (GNUNET_OK != 284 TALER_amount_cmp_currency (&prc->total_refund_amount, 285 refund_amount)) 286 { 287 /* Database error, refunds in mixed currency in DB. Not OK! */ 288 prc->ec = TALER_EC_GENERIC_DB_INVARIANT_FAILURE; 289 GNUNET_break (0); 290 return; 291 } 292 GNUNET_assert (0 <= 293 TALER_amount_add (&prc->total_refund_amount, 294 &prc->total_refund_amount, 295 refund_amount)); 296 if (pending) 297 GNUNET_assert (0 <= 298 TALER_amount_add (&prc->pending_refund_amount, 299 &prc->pending_refund_amount, 300 refund_amount)); 301 } 302 303 304 /** 305 * Add order details to our JSON array. 306 * 307 * @param cls some closure 308 * @param orig_order_id the order this is about 309 * @param order_serial serial ID of the order 310 * @param creation_time when was the order created 311 */ 312 static void 313 add_order (void *cls, 314 const char *orig_order_id, 315 uint64_t order_serial, 316 struct GNUNET_TIME_Timestamp creation_time) 317 { 318 struct TMH_PendingOrder *po = cls; 319 json_t *contract_terms = NULL; 320 struct TALER_PrivateContractHashP h_contract_terms; 321 enum GNUNET_DB_QueryStatus qs; 322 char *order_id = NULL; 323 bool refundable = false; 324 bool paid; 325 bool wired; 326 struct TALER_MERCHANT_Contract *contract = NULL; 327 int16_t choice_index = -1; 328 struct ProcessRefundsClosure prc = { 329 .ec = TALER_EC_NONE 330 }; 331 332 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 333 "Adding order `%s' (%llu) to result set at instance `%s'\n", 334 orig_order_id, 335 (unsigned long long) order_serial, 336 po->instance_id); 337 qs = TMH_db->lookup_order_status_by_serial (TMH_db->cls, 338 po->instance_id, 339 order_serial, 340 &order_id, 341 &h_contract_terms, 342 &paid); 343 if (qs < 0) 344 { 345 GNUNET_break (0); 346 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 347 return; 348 } 349 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 350 { 351 /* Contract terms don't exist, so the order cannot be paid. */ 352 paid = false; 353 if (NULL == orig_order_id) 354 { 355 /* Got a DB trigger about a new proposal, but it 356 was already deleted again. Just ignore the event. */ 357 return; 358 } 359 order_id = GNUNET_strdup (orig_order_id); 360 } 361 362 { 363 /* First try to find the order in the contracts */ 364 uint64_t os; 365 bool session_matches; 366 367 qs = TMH_db->lookup_contract_terms3 (TMH_db->cls, 368 po->instance_id, 369 order_id, 370 NULL, 371 &contract_terms, 372 &os, 373 &paid, 374 &wired, 375 &session_matches, 376 NULL, 377 &choice_index); 378 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 379 GNUNET_break (os == order_serial); 380 } 381 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 382 { 383 /* Might still be unclaimed, so try order table */ 384 struct TALER_MerchantPostDataHashP unused; 385 386 paid = false; 387 wired = false; 388 qs = TMH_db->lookup_order (TMH_db->cls, 389 po->instance_id, 390 order_id, 391 NULL, 392 &unused, 393 &contract_terms); 394 } 395 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 396 { 397 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 398 "Order %llu disappeared during iteration. Skipping.\n", 399 (unsigned long long) order_serial); 400 goto cleanup; 401 } 402 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 403 { 404 GNUNET_break (0); 405 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 406 goto cleanup; 407 } 408 409 contract = TALER_MERCHANT_contract_parse (contract_terms, 410 true); 411 if (NULL == contract) 412 { 413 GNUNET_break (0); 414 po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID; 415 goto cleanup; 416 } 417 418 if (paid) 419 { 420 const struct TALER_Amount *brutto; 421 422 switch (contract->version) 423 { 424 case TALER_MERCHANT_CONTRACT_VERSION_0: 425 brutto = &contract->details.v0.brutto; 426 break; 427 case TALER_MERCHANT_CONTRACT_VERSION_1: 428 { 429 struct TALER_MERCHANT_ContractChoice *choice 430 = &contract->details.v1.choices[choice_index]; 431 432 GNUNET_assert (choice_index < contract->details.v1.choices_len); 433 brutto = &choice->amount; 434 } 435 break; 436 default: 437 GNUNET_break (0); 438 goto cleanup; 439 } 440 GNUNET_assert (GNUNET_OK == 441 TALER_amount_set_zero (brutto->currency, 442 &prc.total_refund_amount)); 443 GNUNET_assert (GNUNET_OK == 444 TALER_amount_set_zero (brutto->currency, 445 &prc.pending_refund_amount)); 446 447 qs = TMH_db->lookup_refunds_detailed (TMH_db->cls, 448 po->instance_id, 449 &h_contract_terms, 450 &process_refunds_cb, 451 &prc); 452 if (0 > qs) 453 { 454 GNUNET_break (0); 455 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 456 goto cleanup; 457 } 458 if (TALER_EC_NONE != prc.ec) 459 { 460 GNUNET_break (0); 461 po->result = prc.ec; 462 goto cleanup; 463 } 464 if (0 > TALER_amount_cmp (&prc.total_refund_amount, 465 brutto)) 466 refundable = true; 467 } 468 469 switch (contract->version) 470 { 471 case TALER_MERCHANT_CONTRACT_VERSION_0: 472 if (TALER_amount_is_zero (&contract->details.v0.brutto) && 473 (po->of.wired != TALER_EXCHANGE_YNA_ALL) ) 474 { 475 /* If we are actually filtering by wire status, 476 and the order was over an amount of zero, 477 do not return it as wire status is not 478 exactly meaningful for orders over zero. */ 479 goto cleanup; 480 } 481 GNUNET_assert ( 482 0 == 483 json_array_append_new ( 484 po->pa, 485 GNUNET_JSON_PACK ( 486 GNUNET_JSON_pack_string ("order_id", 487 contract->order_id), 488 GNUNET_JSON_pack_uint64 ("row_id", 489 order_serial), 490 GNUNET_JSON_pack_timestamp ("timestamp", 491 creation_time), 492 TALER_JSON_pack_amount ( 493 "amount", 494 &contract->details.v0.brutto), 495 GNUNET_JSON_pack_allow_null ( 496 TALER_JSON_pack_amount ( 497 "refund_amount", 498 paid 499 ? &prc.total_refund_amount 500 : NULL)), 501 GNUNET_JSON_pack_allow_null ( 502 TALER_JSON_pack_amount ( 503 "pending_refund_amount", 504 paid 505 ? &prc.pending_refund_amount 506 : NULL)), 507 TALER_JSON_pack_amount ( 508 "amount", 509 &contract->details.v0.brutto), 510 GNUNET_JSON_pack_string ("summary", 511 contract->summary), 512 GNUNET_JSON_pack_bool ("refundable", 513 refundable), 514 GNUNET_JSON_pack_bool ("paid", 515 paid)))); 516 break; 517 case TALER_MERCHANT_CONTRACT_VERSION_1: 518 if (-1 == choice_index) 519 choice_index = 0; /* default choice */ 520 GNUNET_assert (choice_index < contract->details.v1.choices_len); 521 { 522 struct TALER_MERCHANT_ContractChoice *choice 523 = &contract->details.v1.choices[choice_index]; 524 525 GNUNET_assert ( 526 0 == 527 json_array_append_new ( 528 po->pa, 529 GNUNET_JSON_PACK ( 530 GNUNET_JSON_pack_string ("order_id", 531 contract->order_id), 532 GNUNET_JSON_pack_uint64 ("row_id", 533 order_serial), 534 GNUNET_JSON_pack_timestamp ("timestamp", 535 creation_time), 536 TALER_JSON_pack_amount ("amount", 537 &choice->amount), 538 GNUNET_JSON_pack_allow_null ( 539 TALER_JSON_pack_amount ( 540 "refund_amount", 541 paid 542 ? &prc.total_refund_amount 543 : NULL)), 544 GNUNET_JSON_pack_allow_null ( 545 TALER_JSON_pack_amount ( 546 "pending_refund_amount", 547 paid 548 ? &prc.pending_refund_amount 549 : NULL)), 550 GNUNET_JSON_pack_string ("summary", 551 contract->summary), 552 GNUNET_JSON_pack_bool ("refundable", 553 refundable), 554 GNUNET_JSON_pack_bool ("paid", 555 paid)))); 556 } 557 break; 558 default: 559 GNUNET_break (0); 560 goto cleanup; 561 } 562 563 cleanup: 564 json_decref (contract_terms); 565 GNUNET_free (order_id); 566 if (NULL != contract) 567 { 568 TALER_MERCHANT_contract_free (contract); 569 contract = NULL; 570 } 571 } 572 573 574 /** 575 * We have received a trigger from the database 576 * that we should (possibly) resume some requests. 577 * 578 * @param cls a `struct TMH_MerchantInstance` 579 * @param extra a `struct TMH_OrderChangeEventP` 580 * @param extra_size number of bytes in @a extra 581 */ 582 static void 583 resume_by_event (void *cls, 584 const void *extra, 585 size_t extra_size) 586 { 587 struct TMH_MerchantInstance *mi = cls; 588 const struct TMH_OrderChangeEventDetailsP *oce = extra; 589 struct TMH_PendingOrder *pn; 590 enum TMH_OrderStateFlags osf; 591 uint64_t order_serial_id; 592 struct GNUNET_TIME_Timestamp date; 593 594 if (sizeof (*oce) != extra_size) 595 { 596 GNUNET_break (0); 597 return; 598 } 599 osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state); 600 order_serial_id = GNUNET_ntohll (oce->order_serial_id); 601 date = GNUNET_TIME_timestamp_ntoh (oce->execution_date); 602 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 603 "Received notification about new order %llu\n", 604 (unsigned long long) order_serial_id); 605 for (struct TMH_PendingOrder *po = mi->po_head; 606 NULL != po; 607 po = pn) 608 { 609 pn = po->next; 610 if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) == 611 (0 != (osf & TMH_OSF_PAID))) || 612 (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) && 613 ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) == 614 (0 != (osf & TMH_OSF_REFUNDED))) || 615 (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) && 616 ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) == 617 (0 != (osf & TMH_OSF_WIRED))) || 618 (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) ) 619 { 620 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 621 "Client %p waits on different order type\n", 622 po); 623 continue; 624 } 625 if (po->of.delta > 0) 626 { 627 if (order_serial_id < po->of.start_row) 628 { 629 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 630 "Client %p waits on different order row\n", 631 po); 632 continue; 633 } 634 if (GNUNET_TIME_timestamp_cmp (date, 635 <, 636 po->of.date)) 637 { 638 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 639 "Client %p waits on different order date\n", 640 po); 641 continue; 642 } 643 po->of.delta--; 644 } 645 else 646 { 647 if (order_serial_id > po->of.start_row) 648 { 649 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 650 "Client %p waits on different order row\n", 651 po); 652 continue; 653 } 654 if (GNUNET_TIME_timestamp_cmp (date, 655 >, 656 po->of.date)) 657 { 658 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 659 "Client %p waits on different order date\n", 660 po); 661 continue; 662 } 663 po->of.delta++; 664 } 665 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 666 "Waking up client %p!\n", 667 po); 668 add_order (po, 669 NULL, 670 order_serial_id, 671 date); 672 GNUNET_assert (po->in_dll); 673 GNUNET_CONTAINER_DLL_remove (mi->po_head, 674 mi->po_tail, 675 po); 676 po->in_dll = false; 677 GNUNET_assert (po == 678 GNUNET_CONTAINER_heap_remove_node (po->hn)); 679 po->hn = NULL; 680 MHD_resume_connection (po->con); 681 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 682 } 683 if (NULL == mi->po_head) 684 { 685 TMH_db->event_listen_cancel (mi->po_eh); 686 mi->po_eh = NULL; 687 } 688 } 689 690 691 /** 692 * There has been a change or addition of a new @a order_id. Wake up 693 * long-polling clients that may have been waiting for this event. 694 * 695 * @param mi the instance where the order changed 696 * @param osf order state flags 697 * @param date execution date of the order 698 * @param order_serial_id serial ID of the order in the database 699 */ 700 void 701 TMH_notify_order_change (struct TMH_MerchantInstance *mi, 702 enum TMH_OrderStateFlags osf, 703 struct GNUNET_TIME_Timestamp date, 704 uint64_t order_serial_id) 705 { 706 struct TMH_OrderChangeEventDetailsP oce = { 707 .order_serial_id = GNUNET_htonll (order_serial_id), 708 .execution_date = GNUNET_TIME_timestamp_hton (date), 709 .order_state = htonl (osf) 710 }; 711 struct TMH_OrderChangeEventP eh = { 712 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE), 713 .header.size = htons (sizeof (eh)), 714 .merchant_pub = mi->merchant_pub 715 }; 716 717 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 718 "Notifying clients of new order %llu at %s\n", 719 (unsigned long long) order_serial_id, 720 TALER_B2S (&mi->merchant_pub)); 721 TMH_db->event_notify (TMH_db->cls, 722 &eh.header, 723 &oce, 724 sizeof (oce)); 725 } 726 727 728 /** 729 * Transforms an (untrusted) input filter into a Postgresql LIKE filter. 730 * Escapes "%" and "_" in the @a input and adds "%" at the beginning 731 * and the end to turn the @a input into a suitable Postgresql argument. 732 * 733 * @param input text to turn into a substring match expression, or NULL 734 * @return NULL if @a input was NULL, otherwise transformed @a input 735 */ 736 static char * 737 tr (const char *input) 738 { 739 char *out; 740 size_t slen; 741 size_t wpos; 742 743 if (NULL == input) 744 return NULL; 745 slen = strlen (input); 746 out = GNUNET_malloc (slen * 2 + 3); 747 wpos = 0; 748 out[wpos++] = '%'; 749 for (size_t i = 0; i<slen; i++) 750 { 751 char c = input[i]; 752 753 if ( (c == '%') || 754 (c == '_') ) 755 out[wpos++] = '\\'; 756 out[wpos++] = c; 757 } 758 out[wpos++] = '%'; 759 GNUNET_assert (wpos < slen * 2 + 3); 760 return out; 761 } 762 763 764 /** 765 * Handle a GET "/orders" request. 766 * 767 * @param rh context of the handler 768 * @param connection the MHD connection to handle 769 * @param[in,out] hc context with further information about the request 770 * @return MHD result code 771 */ 772 MHD_RESULT 773 TMH_private_get_orders (const struct TMH_RequestHandler *rh, 774 struct MHD_Connection *connection, 775 struct TMH_HandlerContext *hc) 776 { 777 struct TMH_PendingOrder *po = hc->ctx; 778 enum GNUNET_DB_QueryStatus qs; 779 780 if (NULL != po) 781 { 782 /* resumed from long-polling, return answer we already have 783 in 'hc->ctx' */ 784 if (TALER_EC_NONE != po->result) 785 { 786 GNUNET_break (0); 787 return TALER_MHD_reply_with_error (connection, 788 MHD_HTTP_INTERNAL_SERVER_ERROR, 789 po->result, 790 NULL); 791 } 792 return TALER_MHD_REPLY_JSON_PACK ( 793 connection, 794 MHD_HTTP_OK, 795 GNUNET_JSON_pack_array_incref ("orders", 796 po->pa)); 797 } 798 po = GNUNET_new (struct TMH_PendingOrder); 799 hc->ctx = po; 800 hc->cc = &cleanup; 801 po->con = connection; 802 po->pa = json_array (); 803 GNUNET_assert (NULL != po->pa); 804 po->instance_id = hc->instance->settings.id; 805 po->mi = hc->instance; 806 807 if (! (TALER_MHD_arg_to_yna (connection, 808 "paid", 809 TALER_EXCHANGE_YNA_ALL, 810 &po->of.paid)) ) 811 { 812 GNUNET_break_op (0); 813 return TALER_MHD_reply_with_error (connection, 814 MHD_HTTP_BAD_REQUEST, 815 TALER_EC_GENERIC_PARAMETER_MALFORMED, 816 "paid"); 817 } 818 if (! (TALER_MHD_arg_to_yna (connection, 819 "refunded", 820 TALER_EXCHANGE_YNA_ALL, 821 &po->of.refunded)) ) 822 { 823 GNUNET_break_op (0); 824 return TALER_MHD_reply_with_error (connection, 825 MHD_HTTP_BAD_REQUEST, 826 TALER_EC_GENERIC_PARAMETER_MALFORMED, 827 "refunded"); 828 } 829 if (! (TALER_MHD_arg_to_yna (connection, 830 "wired", 831 TALER_EXCHANGE_YNA_ALL, 832 &po->of.wired)) ) 833 { 834 GNUNET_break_op (0); 835 return TALER_MHD_reply_with_error (connection, 836 MHD_HTTP_BAD_REQUEST, 837 TALER_EC_GENERIC_PARAMETER_MALFORMED, 838 "wired"); 839 } 840 po->of.delta = -20; 841 /* deprecated in protocol v12 */ 842 TALER_MHD_parse_request_snumber (connection, 843 "delta", 844 &po->of.delta); 845 /* since protocol v12 */ 846 TALER_MHD_parse_request_snumber (connection, 847 "limit", 848 &po->of.delta); 849 if ( (-MAX_DELTA > po->of.delta) || 850 (po->of.delta > MAX_DELTA) ) 851 { 852 GNUNET_break_op (0); 853 return TALER_MHD_reply_with_error (connection, 854 MHD_HTTP_BAD_REQUEST, 855 TALER_EC_GENERIC_PARAMETER_MALFORMED, 856 "limit"); 857 } 858 { 859 const char *date_s_str; 860 861 date_s_str = MHD_lookup_connection_value (connection, 862 MHD_GET_ARGUMENT_KIND, 863 "date_s"); 864 if (NULL == date_s_str) 865 { 866 if (po->of.delta > 0) 867 po->of.date = GNUNET_TIME_UNIT_ZERO_TS; 868 else 869 po->of.date = GNUNET_TIME_UNIT_FOREVER_TS; 870 } 871 else 872 { 873 char dummy; 874 unsigned long long ll; 875 876 if (1 != 877 sscanf (date_s_str, 878 "%llu%c", 879 &ll, 880 &dummy)) 881 { 882 GNUNET_break_op (0); 883 return TALER_MHD_reply_with_error (connection, 884 MHD_HTTP_BAD_REQUEST, 885 TALER_EC_GENERIC_PARAMETER_MALFORMED, 886 "date_s"); 887 } 888 889 po->of.date = GNUNET_TIME_absolute_to_timestamp ( 890 GNUNET_TIME_absolute_from_s (ll)); 891 if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time)) 892 { 893 GNUNET_break_op (0); 894 return TALER_MHD_reply_with_error (connection, 895 MHD_HTTP_BAD_REQUEST, 896 TALER_EC_GENERIC_PARAMETER_MALFORMED, 897 "date_s"); 898 } 899 } 900 } 901 if (po->of.delta > 0) 902 po->of.start_row = 0; 903 else 904 po->of.start_row = INT64_MAX; 905 /* deprecated in protocol v12 */ 906 TALER_MHD_parse_request_number (connection, 907 "start", 908 &po->of.start_row); 909 /* since protocol v12 */ 910 TALER_MHD_parse_request_number (connection, 911 "offset", 912 &po->of.start_row); 913 if (INT64_MAX < po->of.start_row) 914 { 915 GNUNET_break_op (0); 916 return TALER_MHD_reply_with_error (connection, 917 MHD_HTTP_BAD_REQUEST, 918 TALER_EC_GENERIC_PARAMETER_MALFORMED, 919 "offset"); 920 } 921 po->summary_filter = tr (MHD_lookup_connection_value (connection, 922 MHD_GET_ARGUMENT_KIND, 923 "summary_filter")); 924 po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */ 925 po->of.session_id 926 = MHD_lookup_connection_value (connection, 927 MHD_GET_ARGUMENT_KIND, 928 "session_id"); 929 po->of.fulfillment_url 930 = MHD_lookup_connection_value (connection, 931 MHD_GET_ARGUMENT_KIND, 932 "fulfillment_url"); 933 TALER_MHD_parse_request_timeout (connection, 934 &po->long_poll_timeout); 935 if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout)) 936 { 937 GNUNET_break_op (0); 938 return TALER_MHD_reply_with_error (connection, 939 MHD_HTTP_BAD_REQUEST, 940 TALER_EC_GENERIC_PARAMETER_MALFORMED, 941 "timeout_ms"); 942 } 943 po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout); 944 if ( (0 >= po->of.delta) && 945 (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) ) 946 { 947 GNUNET_break_op (0); 948 po->of.timeout = GNUNET_TIME_UNIT_ZERO; 949 po->long_poll_timeout = GNUNET_TIME_UNIT_ZERO_ABS; 950 } 951 952 qs = TMH_db->lookup_orders (TMH_db->cls, 953 po->instance_id, 954 &po->of, 955 &add_order, 956 po); 957 if (0 > qs) 958 { 959 GNUNET_break (0); 960 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 961 } 962 if (TALER_EC_NONE != po->result) 963 { 964 GNUNET_break (0); 965 return TALER_MHD_reply_with_error (connection, 966 MHD_HTTP_INTERNAL_SERVER_ERROR, 967 po->result, 968 NULL); 969 } 970 if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) && 971 (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) ) 972 { 973 struct TMH_MerchantInstance *mi = hc->instance; 974 975 /* setup timeout heap (if not yet exists) */ 976 if (NULL == order_timeout_heap) 977 order_timeout_heap 978 = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN); 979 po->hn = GNUNET_CONTAINER_heap_insert (order_timeout_heap, 980 po, 981 po->long_poll_timeout.abs_value_us); 982 GNUNET_CONTAINER_DLL_insert (mi->po_head, 983 mi->po_tail, 984 po); 985 po->in_dll = true; 986 if (NULL == mi->po_eh) 987 { 988 struct TMH_OrderChangeEventP change_eh = { 989 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE), 990 .header.size = htons (sizeof (change_eh)), 991 .merchant_pub = mi->merchant_pub 992 }; 993 994 mi->po_eh = TMH_db->event_listen (TMH_db->cls, 995 &change_eh.header, 996 GNUNET_TIME_UNIT_FOREVER_REL, 997 &resume_by_event, 998 mi); 999 } 1000 MHD_suspend_connection (connection); 1001 { 1002 struct TMH_PendingOrder *pot; 1003 1004 /* start timeout task */ 1005 pot = GNUNET_CONTAINER_heap_peek (order_timeout_heap); 1006 if (NULL != order_timeout_task) 1007 GNUNET_SCHEDULER_cancel (order_timeout_task); 1008 order_timeout_task = GNUNET_SCHEDULER_add_at (pot->long_poll_timeout, 1009 &order_timeout, 1010 NULL); 1011 } 1012 return MHD_YES; 1013 } 1014 return TALER_MHD_REPLY_JSON_PACK ( 1015 connection, 1016 MHD_HTTP_OK, 1017 GNUNET_JSON_pack_array_incref ("orders", 1018 po->pa)); 1019 } 1020 1021 1022 /* end of taler-merchant-httpd_private-get-orders.c */