taler-merchant-httpd_get-private-orders.c (54245B)
1 /* 2 This file is part of TALER 3 (C) 2019--2026 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 src/backend/taler-merchant-httpd_get-private-orders.c 18 * @brief implement GET /orders 19 * @author Christian Grothoff 20 * 21 * FIXME-cleanup: consider introducing phases / state machine 22 */ 23 #include "platform.h" 24 #include "taler-merchant-httpd_get-private-orders.h" 25 #include <taler/taler_merchant_util.h> 26 #include <taler/taler_json_lib.h> 27 #include <taler/taler_dbevents.h> 28 #include "merchant-database/get_contract_terms_status.h" 29 #include "merchant-database/get_order.h" 30 #include "merchant-database/set_instance.h" 31 #include "merchant-database/get_order_status_by_serial.h" 32 #include "merchant-database/iterate_orders.h" 33 #include "merchant-database/iterate_refunds_detailed.h" 34 #include "merchant-database/event_listen.h" 35 #include "merchant-database/preflight.h" 36 #include "merchant-database/event_notify.h" 37 38 39 /** 40 * Sensible bound on TALER_MERCHANTDB_OrderFilter.delta 41 */ 42 #define MAX_DELTA 1024 43 44 #define CSV_HEADER \ 45 "Order ID,Row,YYYY-MM-DD,HH:MM,Timestamp,Amount,Refund amount,Pending refund amount,Summary,Refundable,Paid\r\n" 46 #define CSV_FOOTER "\r\n" 47 48 #define XML_HEADER "<?xml version=\"1.0\"?>" \ 49 "<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"" \ 50 " xmlns:c=\"urn:schemas-microsoft-com:office:component:spreadsheet\"" \ 51 " xmlns:html=\"http://www.w3.org/TR/REC-html40\"" \ 52 " xmlns:x2=\"http://schemas.microsoft.com/office/excel/2003/xml\"" \ 53 " xmlns:o=\"urn:schemas-microsoft-com:office:office\"" \ 54 " xmlns:x=\"urn:schemas-microsoft-com:office:excel\"" \ 55 " xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">" \ 56 "<Styles>" \ 57 "<Style ss:ID=\"DateFormat\"><NumberFormat ss:Format=\"yyyy-mm-dd hh:mm:ss\"/></Style>" \ 58 "<Style ss:ID=\"Total\"><Font ss:Bold=\"1\"/></Style>" \ 59 "</Styles>\n" \ 60 "<Worksheet ss:Name=\"Orders\">\n" \ 61 "<Table>\n" \ 62 "<Row>\n" \ 63 "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Order ID</Data></Cell>\n" \ 64 "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Timestamp</Data></Cell>\n" \ 65 "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Price</Data></Cell>\n" \ 66 "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Refunded</Data></Cell>\n" \ 67 "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Summary</Data></Cell>\n" \ 68 "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Paid</Data></Cell>\n" \ 69 "</Row>\n" 70 #define XML_FOOTER "</Table></Worksheet></Workbook>" 71 72 73 /** 74 * A pending GET /orders request. 75 */ 76 struct TMH_PendingOrder 77 { 78 79 /** 80 * Kept in a DLL. 81 */ 82 struct TMH_PendingOrder *prev; 83 84 /** 85 * Kept in a DLL. 86 */ 87 struct TMH_PendingOrder *next; 88 89 /** 90 * Which connection was suspended. 91 */ 92 struct MHD_Connection *con; 93 94 /** 95 * Which instance is this client polling? This also defines 96 * which DLL this struct is part of. 97 */ 98 struct TMH_MerchantInstance *mi; 99 100 /** 101 * At what time does this request expire? If set in the future, we 102 * may wait this long for a payment to arrive before responding. 103 */ 104 struct GNUNET_TIME_Absolute long_poll_timeout; 105 106 /** 107 * Filter to apply. 108 */ 109 struct TALER_MERCHANTDB_OrderFilter of; 110 111 /** 112 * The array of orders (used for JSON and PDF/Typst). 113 */ 114 json_t *pa; 115 116 /** 117 * Running total of order amounts, for totals row in CSV/XML/PDF. 118 * Initialised to zero on first order seen. 119 */ 120 struct TALER_AmountSet total_amount; 121 122 /** 123 * Running total of granted refund amounts. 124 * Initialised to zero on first paid order seen. 125 */ 126 struct TALER_AmountSet total_refund_amount; 127 128 /** 129 * Running total of pending refund amounts. 130 * Initialised to zero on first paid order seen. 131 */ 132 struct TALER_AmountSet total_pending_refund_amount; 133 134 /** 135 * The name of the instance we are querying for. 136 */ 137 const char *instance_id; 138 139 /** 140 * Alias of @a of.summary_filter, but with memory to be released (owner). 141 */ 142 char *summary_filter; 143 144 /** 145 * The result after adding the orders (#TALER_EC_NONE for okay, anything else for an error). 146 */ 147 enum TALER_ErrorCode result; 148 149 /** 150 * Is the structure in the DLL 151 */ 152 bool in_dll; 153 154 /** 155 * Output format requested by the client. 156 */ 157 enum 158 { 159 POF_JSON, 160 POF_CSV, 161 POF_XML, 162 POF_PDF 163 } format; 164 165 /** 166 * Buffer used when format is #POF_CSV. 167 */ 168 struct GNUNET_Buffer csv; 169 170 /** 171 * Buffer used when format is #POF_XML. 172 */ 173 struct GNUNET_Buffer xml; 174 175 /** 176 * Async context used to run Typst (for #POF_PDF). 177 */ 178 struct TALER_MHD_TypstContext *tc; 179 180 /** 181 * Pre-built MHD response (used when #POF_PDF Typst is done). 182 */ 183 struct MHD_Response *response; 184 185 /** 186 * Task to timeout pending order. 187 */ 188 struct GNUNET_SCHEDULER_Task *order_timeout_task; 189 190 /** 191 * True if a suspended request must run its database query again instead of 192 * replying with the response accumulated so far. 193 */ 194 bool recheck; 195 196 /** 197 * HTTP status to return with @e response. 198 */ 199 unsigned int http_status; 200 }; 201 202 203 /** 204 * DLL head for requests suspended waiting for Typst. 205 */ 206 static struct TMH_PendingOrder *pdf_head; 207 208 /** 209 * DLL tail for requests suspended waiting for Typst. 210 */ 211 static struct TMH_PendingOrder *pdf_tail; 212 213 214 void 215 TMH_force_get_orders_resume (struct TMH_MerchantInstance *mi) 216 { 217 struct TMH_PendingOrder *po; 218 219 while (NULL != (po = mi->po_head)) 220 { 221 GNUNET_assert (po->in_dll); 222 GNUNET_CONTAINER_DLL_remove (mi->po_head, 223 mi->po_tail, 224 po); 225 MHD_resume_connection (po->con); 226 po->in_dll = false; 227 } 228 if (NULL != mi->po_eh) 229 { 230 TALER_MERCHANTDB_event_listen_cancel (mi->po_eh); 231 mi->po_eh = NULL; 232 } 233 } 234 235 236 void 237 TMH_force_get_orders_resume_typst () 238 { 239 struct TMH_PendingOrder *po; 240 241 while (NULL != (po = pdf_head)) 242 { 243 GNUNET_CONTAINER_DLL_remove (pdf_head, 244 pdf_tail, 245 po); 246 MHD_resume_connection (po->con); 247 } 248 } 249 250 251 /** 252 * Task run to trigger timeouts on GET /orders requests with long polling. 253 * 254 * @param cls a `struct TMH_PendingOrder *` 255 */ 256 static void 257 order_timeout (void *cls) 258 { 259 struct TMH_PendingOrder *po = cls; 260 struct TMH_MerchantInstance *mi = po->mi; 261 262 po->order_timeout_task = NULL; 263 po->recheck = true; 264 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 265 "Resuming long polled job due to timeout\n"); 266 GNUNET_assert (po->in_dll); 267 GNUNET_CONTAINER_DLL_remove (mi->po_head, 268 mi->po_tail, 269 po); 270 po->in_dll = false; 271 MHD_resume_connection (po->con); 272 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 273 } 274 275 276 /** 277 * Cleanup our "context", where we stored the data 278 * we are building for the response. 279 * 280 * @param ctx context to clean up, must be a `struct TMH_PendingOrder *` 281 */ 282 static void 283 cleanup (void *ctx) 284 { 285 struct TMH_PendingOrder *po = ctx; 286 287 if (po->in_dll) 288 { 289 struct TMH_MerchantInstance *mi = po->mi; 290 291 GNUNET_CONTAINER_DLL_remove (mi->po_head, 292 mi->po_tail, 293 po); 294 MHD_resume_connection (po->con); 295 } 296 if (NULL != po->order_timeout_task) 297 { 298 GNUNET_SCHEDULER_cancel (po->order_timeout_task); 299 po->order_timeout_task = NULL; 300 } 301 json_decref (po->pa); 302 TALER_amount_set_free (&po->total_amount); 303 TALER_amount_set_free (&po->total_refund_amount); 304 TALER_amount_set_free (&po->total_pending_refund_amount); 305 GNUNET_free (po->summary_filter); 306 switch (po->format) 307 { 308 case POF_JSON: 309 break; 310 case POF_CSV: 311 GNUNET_buffer_clear (&po->csv); 312 break; 313 case POF_XML: 314 GNUNET_buffer_clear (&po->xml); 315 break; 316 case POF_PDF: 317 if (NULL != po->tc) 318 { 319 TALER_MHD_typst_cancel (po->tc); 320 po->tc = NULL; 321 } 322 break; 323 } 324 if (NULL != po->response) 325 { 326 MHD_destroy_response (po->response); 327 po->response = NULL; 328 } 329 GNUNET_free (po); 330 } 331 332 333 /** 334 * Closure for #process_refunds_cb(). 335 */ 336 struct ProcessRefundsClosure 337 { 338 /** 339 * Place where we accumulate the granted refunds. 340 */ 341 struct TALER_Amount total_refund_amount; 342 343 /** 344 * Place where we accumulate the pending refunds. 345 */ 346 struct TALER_Amount pending_refund_amount; 347 348 /** 349 * Set to an error code if something goes wrong. 350 */ 351 enum TALER_ErrorCode ec; 352 }; 353 354 355 /** 356 * Function called with information about a refund. 357 * It is responsible for summing up the refund amount. 358 * 359 * @param cls closure 360 * @param refund_serial unique serial number of the refund 361 * @param timestamp time of the refund (for grouping of refunds in the wallet UI) 362 * @param coin_pub public coin from which the refund comes from 363 * @param exchange_url URL of the exchange that issued @a coin_pub 364 * @param rtransaction_id identificator of the refund 365 * @param reason human-readable explanation of the refund 366 * @param refund_amount refund amount which is being taken from @a coin_pub 367 * @param pending true if the this refund was not yet processed by the wallet/exchange 368 */ 369 static void 370 process_refunds_cb (void *cls, 371 uint64_t refund_serial, 372 struct GNUNET_TIME_Timestamp timestamp, 373 const struct TALER_CoinSpendPublicKeyP *coin_pub, 374 const char *exchange_url, 375 uint64_t rtransaction_id, 376 const char *reason, 377 const struct TALER_Amount *refund_amount, 378 bool pending) 379 { 380 struct ProcessRefundsClosure *prc = cls; 381 382 if (GNUNET_OK != 383 TALER_amount_cmp_currency (&prc->total_refund_amount, 384 refund_amount)) 385 { 386 /* Database error, refunds in mixed currency in DB. Not OK! */ 387 prc->ec = TALER_EC_GENERIC_DB_INVARIANT_FAILURE; 388 GNUNET_break (0); 389 return; 390 } 391 GNUNET_assert (0 <= 392 TALER_amount_add (&prc->total_refund_amount, 393 &prc->total_refund_amount, 394 refund_amount)); 395 if (pending) 396 GNUNET_assert (0 <= 397 TALER_amount_add (&prc->pending_refund_amount, 398 &prc->pending_refund_amount, 399 refund_amount)); 400 } 401 402 403 /** 404 * Add one order entry to the running order-amount total in @a po. 405 * Sets po->result to TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT on overflow. 406 * 407 * @param[in,out] po pending order accumulator 408 * @param amount the order amount to add 409 */ 410 static void 411 accumulate_total (struct TMH_PendingOrder *po, 412 const struct TALER_Amount *amount) 413 { 414 if (0 > TALER_amount_set_add (&po->total_amount, 415 amount, 416 NULL)) 417 { 418 GNUNET_break (0); 419 po->result = TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT; 420 } 421 } 422 423 424 /** 425 * Add refund amounts to the running refund totals in @a po. 426 * Sets po->result to TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT on overflow. 427 * Only called for paid orders (where refund tracking is meaningful). 428 * 429 * @param[in,out] po pending order accumulator 430 * @param refund granted refund amount for this order 431 * @param pending pending (not-yet-processed) refund amount for this order 432 */ 433 static void 434 accumulate_refund_totals (struct TMH_PendingOrder *po, 435 const struct TALER_Amount *refund, 436 const struct TALER_Amount *pending) 437 { 438 if (TALER_EC_NONE != po->result) 439 return; 440 if (0 > TALER_amount_set_add (&po->total_refund_amount, 441 refund, 442 NULL)) 443 { 444 GNUNET_break (0); 445 po->result = TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT; 446 return; 447 } 448 if (0 > TALER_amount_set_add (&po->total_pending_refund_amount, 449 pending, 450 NULL)) 451 { 452 GNUNET_break (0); 453 po->result = TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT; 454 } 455 } 456 457 458 /** 459 * Add order details to our response accumulator. 460 * 461 * @param cls some closure 462 * @param orig_order_id the order this is about 463 * @param order_serial serial ID of the order 464 * @param creation_time when was the order created 465 */ 466 static void 467 add_order (void *cls, 468 const char *orig_order_id, 469 uint64_t order_serial, 470 struct GNUNET_TIME_Timestamp creation_time) 471 { 472 struct TMH_PendingOrder *po = cls; 473 json_t *terms = NULL; 474 struct TALER_PrivateContractHashP h_contract_terms; 475 enum GNUNET_DB_QueryStatus qs; 476 char *order_id = NULL; 477 bool refundable = false; 478 bool paid; 479 bool wired; 480 struct TALER_MERCHANT_Contract *contract = NULL; 481 struct TALER_MERCHANT_Order *order = NULL; 482 int16_t choice_index = -1; 483 struct ProcessRefundsClosure prc = { 484 .ec = TALER_EC_NONE 485 }; 486 const struct TALER_Amount *amount; 487 char amount_buf[128]; 488 char refund_buf[128]; 489 char pending_buf[128]; 490 const struct TALER_MERCHANT_ContractBaseTerms *ct = NULL; 491 492 /* Bail early if we already have an error */ 493 if (TALER_EC_NONE != po->result) 494 return; 495 496 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 497 "Adding order `%s' (%llu) to result set at instance `%s'\n", 498 orig_order_id, 499 (unsigned long long) order_serial, 500 po->instance_id); 501 qs = TALER_MERCHANTDB_set_instance ( 502 TMH_db, 503 po->instance_id); 504 if (qs <= 0) 505 { 506 GNUNET_break (0); 507 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 508 return; 509 } 510 qs = TALER_MERCHANTDB_get_order_status_by_serial (TMH_db, 511 po->instance_id, 512 order_serial, 513 &order_id, 514 &h_contract_terms, 515 &paid); 516 if (qs < 0) 517 { 518 GNUNET_break (0); 519 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 520 goto cleanup; 521 } 522 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 523 { 524 /* Contract terms don't exist, so the order cannot be paid. */ 525 paid = false; 526 if (NULL == orig_order_id) 527 { 528 /* Got a DB trigger about a new proposal, but it 529 was already deleted again. Just ignore the event. */ 530 GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 531 TALER_MERCHANTDB_set_instance ( 532 TMH_db, 533 NULL)); 534 return; 535 } 536 order_id = GNUNET_strdup (orig_order_id); 537 } 538 539 { 540 /* First try to find the order in the contracts */ 541 uint64_t os; 542 bool session_matches; 543 544 qs = TALER_MERCHANTDB_get_contract_terms_status (TMH_db, 545 po->instance_id, 546 order_id, 547 NULL, 548 &terms, 549 &os, 550 &paid, 551 &wired, 552 &session_matches, 553 NULL, 554 &choice_index); 555 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 556 { 557 GNUNET_break (os == order_serial); 558 contract = TALER_MERCHANT_contract_parse (terms); 559 if (NULL == contract) 560 { 561 GNUNET_break (0); 562 po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID; 563 goto cleanup; 564 } 565 ct = contract->pc->base; 566 } 567 } 568 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 569 { 570 /* Might still be unclaimed, so try order table */ 571 struct TALER_MerchantPostDataHashP unused; 572 573 paid = false; 574 wired = false; 575 qs = TALER_MERCHANTDB_get_order (TMH_db, 576 po->instance_id, 577 order_id, 578 NULL, 579 &unused, 580 &terms); 581 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 582 { 583 order = TALER_MERCHANT_order_parse (terms); 584 if (NULL == order) 585 { 586 GNUNET_break (0); 587 po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID; 588 goto cleanup; 589 } 590 ct = order->base; 591 } 592 } 593 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 594 { 595 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 596 "Order %llu disappeared during iteration. Skipping.\n", 597 (unsigned long long) order_serial); 598 goto cleanup; 599 } 600 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 601 { 602 GNUNET_break (0); 603 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 604 goto cleanup; 605 } 606 607 608 if (paid) 609 { 610 const struct TALER_Amount *brutto; 611 612 GNUNET_assert (NULL != contract); 613 switch (ct->version) 614 { 615 case TALER_MERCHANT_CONTRACT_VERSION_0: 616 brutto = &contract->pc->details.v0.brutto; 617 break; 618 case TALER_MERCHANT_CONTRACT_VERSION_1: 619 { 620 struct TALER_MERCHANT_ContractChoice *choice; 621 622 if (-1 == choice_index) 623 choice_index = 0; /* default choice */ 624 GNUNET_assert (choice_index < contract->pc->details.v1.choices_len); 625 choice = &contract->pc->details.v1.choices[choice_index]; 626 brutto = &choice->amount; 627 } 628 break; 629 default: 630 GNUNET_break (0); 631 goto cleanup; 632 } 633 GNUNET_assert (GNUNET_OK == 634 TALER_amount_set_zero (brutto->currency, 635 &prc.total_refund_amount)); 636 GNUNET_assert (GNUNET_OK == 637 TALER_amount_set_zero (brutto->currency, 638 &prc.pending_refund_amount)); 639 640 qs = TALER_MERCHANTDB_iterate_refunds_detailed (TMH_db, 641 po->instance_id, 642 &h_contract_terms, 643 &process_refunds_cb, 644 &prc); 645 if (0 > qs) 646 { 647 GNUNET_break (0); 648 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 649 goto cleanup; 650 } 651 if (TALER_EC_NONE != prc.ec) 652 { 653 GNUNET_break (0); 654 po->result = prc.ec; 655 goto cleanup; 656 } 657 if (0 > TALER_amount_cmp (&prc.total_refund_amount, 658 brutto) && 659 GNUNET_TIME_absolute_is_future ( 660 contract->pc->refund_deadline.abs_time)) 661 refundable = true; 662 } 663 664 /* compute amount totals */ 665 amount = NULL; 666 switch (ct->version) 667 { 668 case TALER_MERCHANT_CONTRACT_VERSION_0: 669 { 670 amount = (NULL != contract) 671 ? &contract->pc->details.v0.brutto 672 : &order->details.v0.brutto; 673 674 if (TALER_amount_is_zero (amount) && 675 (po->of.wired != TALER_EXCHANGE_YNA_ALL) ) 676 { 677 /* If we are actually filtering by wire status, 678 and the order was over an amount of zero, 679 do not return it as wire status is not 680 exactly meaningful for orders over zero. */ 681 goto cleanup; 682 } 683 684 /* Accumulate order total */ 685 if (paid) 686 accumulate_total (po, 687 amount); 688 if (TALER_EC_NONE != po->result) 689 goto cleanup; 690 /* Accumulate refund totals (only meaningful for paid orders) */ 691 if (paid) 692 { 693 accumulate_refund_totals (po, 694 &prc.total_refund_amount, 695 &prc.pending_refund_amount); 696 if (TALER_EC_NONE != po->result) 697 goto cleanup; 698 } 699 } 700 break; 701 case TALER_MERCHANT_CONTRACT_VERSION_1: 702 if (-1 == choice_index) 703 choice_index = 0; /* default choice */ 704 if (NULL != contract) 705 { 706 struct TALER_MERCHANT_ContractChoice *choice 707 = &contract->pc->details.v1.choices[choice_index]; 708 709 GNUNET_assert (choice_index < contract->pc->details.v1.choices_len); 710 amount = &choice->amount; 711 /* Accumulate order total */ 712 if (paid) 713 accumulate_total (po, 714 amount); 715 if (TALER_EC_NONE != po->result) 716 goto cleanup; 717 /* Accumulate refund totals (only meaningful for paid orders) */ 718 if (paid) 719 { 720 accumulate_refund_totals (po, 721 &prc.total_refund_amount, 722 &prc.pending_refund_amount); 723 if (TALER_EC_NONE != po->result) 724 goto cleanup; 725 } 726 } 727 else 728 { 729 GNUNET_assert (choice_index < order->details.v1.choices_len); 730 amount = &order->details.v1.choices[choice_index].amount; 731 if (TALER_EC_NONE != po->result) 732 goto cleanup; 733 } 734 break; 735 default: 736 GNUNET_break (0); 737 po->result = TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION; 738 goto cleanup; 739 } 740 741 /* convert amounts to strings (needed for some formats) */ 742 /* FIXME: use currency formatting rules in the future 743 instead of TALER_amount2s for human readability... */ 744 strcpy (amount_buf, 745 TALER_amount2s (amount)); 746 if (paid) 747 strcpy (refund_buf, 748 TALER_amount2s (&prc.total_refund_amount)); 749 if (paid) 750 strcpy (pending_buf, 751 TALER_amount2s (&prc.pending_refund_amount)); 752 753 switch (po->format) 754 { 755 case POF_JSON: 756 case POF_PDF: 757 GNUNET_assert ( 758 0 == 759 json_array_append_new ( 760 po->pa, 761 GNUNET_JSON_PACK ( 762 GNUNET_JSON_pack_string ("order_id", 763 order_id), 764 GNUNET_JSON_pack_uint64 ("row_id", 765 order_serial), 766 GNUNET_JSON_pack_timestamp ("timestamp", 767 creation_time), 768 GNUNET_JSON_pack_timestamp ( 769 "pay_deadline", 770 (NULL != contract) 771 ? contract->pc->pay_deadline 772 : order->pay_deadline), 773 TALER_JSON_pack_amount ("amount", 774 amount), 775 GNUNET_JSON_pack_allow_null ( 776 TALER_JSON_pack_amount ( 777 "refund_amount", 778 paid 779 ? &prc.total_refund_amount 780 : NULL)), 781 GNUNET_JSON_pack_allow_null ( 782 TALER_JSON_pack_amount ( 783 "pending_refund_amount", 784 paid 785 ? &prc.pending_refund_amount 786 : NULL)), 787 GNUNET_JSON_pack_string ("summary", 788 ct->summary), 789 GNUNET_JSON_pack_bool ("refundable", 790 refundable), 791 GNUNET_JSON_pack_bool ("paid", 792 paid), 793 GNUNET_JSON_pack_bool ("wired", 794 wired)))); 795 break; 796 case POF_CSV: 797 { 798 size_t len = strlen (ct->summary); 799 size_t wpos = 0; 800 char *esummary; 801 struct tm *tm; 802 time_t t; 803 804 /* Escape 'summary' to double '"' as per RFC 4180, 2.7. */ 805 esummary = GNUNET_malloc (2 * len + 1); 806 for (size_t off = 0; off<len; off++) 807 { 808 if ('"' == ct->summary[off]) 809 esummary[wpos++] = '"'; 810 esummary[wpos++] = ct->summary[off]; 811 } 812 t = GNUNET_TIME_timestamp_to_s (creation_time); 813 tm = localtime (&t); 814 GNUNET_buffer_write_fstr ( 815 &po->csv, 816 "%s,%llu,%04u-%02u-%02u,%02u:%02u (%s),%llu,%s,%s,%s,\"%s\",%s,%s\r\n", 817 order_id, 818 (unsigned long long) order_serial, 819 tm->tm_year + 1900, 820 tm->tm_mon + 1, 821 tm->tm_mday, 822 tm->tm_hour, 823 tm->tm_min, 824 tm->tm_zone, 825 (unsigned long long) t, 826 amount_buf, 827 paid ? refund_buf : "", 828 paid ? pending_buf : "", 829 esummary, 830 refundable ? "yes" : "no", 831 paid ? "yes" : "no"); 832 GNUNET_free (esummary); 833 break; 834 } 835 case POF_XML: 836 { 837 char *esummary = TALER_escape_xml (ct->summary); 838 char creation_time_s[128]; 839 const struct tm *tm; 840 time_t tt; 841 842 tt = (time_t) GNUNET_TIME_timestamp_to_s (creation_time); 843 tm = gmtime (&tt); 844 strftime (creation_time_s, 845 sizeof (creation_time_s), 846 "%Y-%m-%dT%H:%M:%S", 847 tm); 848 GNUNET_buffer_write_fstr ( 849 &po->xml, 850 "<Row>" 851 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 852 "<Cell ss:StyleID=\"DateFormat\"><Data ss:Type=\"DateTime\">%s</Data></Cell>" 853 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 854 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 855 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 856 "<Cell ss:Formula=\"=%s()\"><Data ss:Type=\"Boolean\">%s</Data></Cell>" 857 "</Row>\n", 858 order_id, 859 creation_time_s, 860 amount_buf, 861 paid ? refund_buf : "", 862 NULL != esummary ? esummary : "", 863 paid ? "TRUE" : "FALSE", 864 paid ? "1" : "0"); 865 GNUNET_free (esummary); 866 } 867 break; 868 } /* end switch po->format */ 869 870 cleanup: 871 GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 872 TALER_MERCHANTDB_set_instance ( 873 TMH_db, 874 NULL)); 875 json_decref (terms); 876 GNUNET_free (order_id); 877 if (NULL != order) 878 { 879 TALER_MERCHANT_order_free (order); 880 order = NULL; 881 } 882 if (NULL != contract) 883 { 884 TALER_MERCHANT_contract_free (contract); 885 contract = NULL; 886 } 887 } 888 889 890 /** 891 * We have received a trigger from the database 892 * that we should (possibly) resume some requests. 893 * 894 * @param cls a `struct TMH_MerchantInstance` 895 * @param extra a `struct TMH_OrderChangeEventP` 896 * @param extra_size number of bytes in @a extra 897 */ 898 static void 899 resume_by_event (void *cls, 900 const void *extra, 901 size_t extra_size) 902 { 903 struct TMH_MerchantInstance *mi = cls; 904 const struct TMH_OrderChangeEventDetailsP *oce = extra; 905 struct TMH_PendingOrder *pn; 906 enum TMH_OrderStateFlags osf; 907 uint64_t order_serial_id; 908 struct GNUNET_TIME_Timestamp date; 909 910 if (sizeof (*oce) != extra_size) 911 { 912 GNUNET_break (0); 913 return; 914 } 915 osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state); 916 order_serial_id = GNUNET_ntohll (oce->order_serial_id); 917 date = GNUNET_TIME_timestamp_ntoh (oce->execution_date); 918 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 919 "Received notification about order %llu\n", 920 (unsigned long long) order_serial_id); 921 for (struct TMH_PendingOrder *po = mi->po_head; 922 NULL != po; 923 po = pn) 924 { 925 pn = po->next; 926 if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) == 927 (0 != (osf & TMH_OSF_PAID))) || 928 (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) && 929 ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) == 930 (0 != (osf & TMH_OSF_REFUNDED))) || 931 (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) && 932 ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) == 933 (0 != (osf & TMH_OSF_WIRED))) || 934 (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) ) 935 { 936 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 937 "Client %p waits on different order type\n", 938 po); 939 continue; 940 } 941 if (po->of.delta > 0) 942 { 943 if (order_serial_id < po->of.start_row) 944 { 945 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 946 "Client %p waits on different order row\n", 947 po); 948 continue; 949 } 950 if (GNUNET_TIME_timestamp_cmp (date, 951 <, 952 po->of.date)) 953 { 954 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 955 "Client %p waits on different order date\n", 956 po); 957 continue; 958 } 959 if (TALER_EXCHANGE_YNA_ALL == po->of.expired) 960 po->of.delta--; 961 } 962 else 963 { 964 if (order_serial_id > po->of.start_row) 965 { 966 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 967 "Client %p waits on different order row\n", 968 po); 969 continue; 970 } 971 if (GNUNET_TIME_timestamp_cmp (date, 972 >, 973 po->of.date)) 974 { 975 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 976 "Client %p waits on different order date\n", 977 po); 978 continue; 979 } 980 if (TALER_EXCHANGE_YNA_ALL == po->of.expired) 981 po->of.delta++; 982 } 983 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 984 "Waking up client %p!\n", 985 po); 986 if (TALER_EXCHANGE_YNA_ALL != po->of.expired) 987 { 988 /* The event flags do not carry a payment deadline. Re-run the query 989 so the expiration predicate is evaluated by the database. */ 990 po->recheck = true; 991 } 992 else 993 { 994 add_order (po, 995 NULL, 996 order_serial_id, 997 date); 998 } 999 GNUNET_assert (po->in_dll); 1000 GNUNET_CONTAINER_DLL_remove (mi->po_head, 1001 mi->po_tail, 1002 po); 1003 po->in_dll = false; 1004 MHD_resume_connection (po->con); 1005 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 1006 } 1007 if (NULL == mi->po_head) 1008 { 1009 TALER_MERCHANTDB_event_listen_cancel (mi->po_eh); 1010 mi->po_eh = NULL; 1011 } 1012 } 1013 1014 1015 /** 1016 * There has been a change or addition of a new @a order_id. Wake up 1017 * long-polling clients that may have been waiting for this event. 1018 * 1019 * @param mi the instance where the order changed 1020 * @param osf order state flags 1021 * @param date execution date of the order 1022 * @param order_serial_id serial ID of the order in the database 1023 */ 1024 void 1025 TMH_notify_order_change (struct TMH_MerchantInstance *mi, 1026 enum TMH_OrderStateFlags osf, 1027 struct GNUNET_TIME_Timestamp date, 1028 uint64_t order_serial_id) 1029 { 1030 struct TMH_OrderChangeEventDetailsP oce = { 1031 .order_serial_id = GNUNET_htonll (order_serial_id), 1032 .execution_date = GNUNET_TIME_timestamp_hton (date), 1033 .order_state = htonl (osf) 1034 }; 1035 struct TMH_OrderChangeEventP eh = { 1036 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE), 1037 .header.size = htons (sizeof (eh)), 1038 .merchant_pub = mi->merchant_pub 1039 }; 1040 1041 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1042 "Notifying clients of new order %llu at %s\n", 1043 (unsigned long long) order_serial_id, 1044 TALER_B2S (&mi->merchant_pub)); 1045 TALER_MERCHANTDB_event_notify (TMH_db, 1046 &eh.header, 1047 &oce, 1048 sizeof (oce)); 1049 } 1050 1051 1052 /** 1053 * Transforms an (untrusted) input filter into a Postgresql LIKE filter. 1054 * Escapes "%" and "_" in the @a input and adds "%" at the beginning 1055 * and the end to turn the @a input into a suitable Postgresql argument. 1056 * 1057 * @param input text to turn into a substring match expression, or NULL 1058 * @return NULL if @a input was NULL, otherwise transformed @a input 1059 */ 1060 static char * 1061 tr (const char *input) 1062 { 1063 char *out; 1064 size_t slen; 1065 size_t wpos; 1066 1067 if (NULL == input) 1068 return NULL; 1069 slen = strlen (input); 1070 out = GNUNET_malloc (slen * 2 + 3); 1071 wpos = 0; 1072 out[wpos++] = '%'; 1073 for (size_t i = 0; i<slen; i++) 1074 { 1075 char c = input[i]; 1076 1077 if ( (c == '%') || 1078 (c == '_') ) 1079 out[wpos++] = '\\'; 1080 out[wpos++] = c; 1081 } 1082 out[wpos++] = '%'; 1083 GNUNET_assert (wpos < slen * 2 + 3); 1084 return out; 1085 } 1086 1087 1088 /** 1089 * Function called with the result of a #TALER_MHD_typst() operation. 1090 * 1091 * @param cls closure, a `struct TMH_PendingOrder *` 1092 * @param tr result of the operation 1093 */ 1094 static void 1095 pdf_cb (void *cls, 1096 const struct TALER_MHD_TypstResponse *tr) 1097 { 1098 struct TMH_PendingOrder *po = cls; 1099 1100 po->tc = NULL; 1101 GNUNET_CONTAINER_DLL_remove (pdf_head, 1102 pdf_tail, 1103 po); 1104 if (TALER_EC_NONE != tr->ec) 1105 { 1106 po->http_status 1107 = TALER_ErrorCode_get_http_status (tr->ec); 1108 po->response 1109 = TALER_MHD_make_error (tr->ec, 1110 tr->details.hint); 1111 } 1112 else 1113 { 1114 po->http_status = MHD_HTTP_OK; 1115 po->response = TALER_MHD_response_from_pdf_file (tr->details.filename); 1116 } 1117 MHD_resume_connection (po->con); 1118 TALER_MHD_daemon_trigger (); 1119 } 1120 1121 1122 /** 1123 * Build the final response for a completed (non-long-poll) request and 1124 * queue it on @a connection. 1125 * 1126 * Handles all formats (JSON, CSV, XML, PDF). For PDF this may suspend 1127 * the connection while Typst runs asynchronously; in that case the caller 1128 * must return #MHD_YES immediately. 1129 * 1130 * @param po the pending order state (already fully populated) 1131 * @param connection the MHD connection 1132 * @param mi the merchant instance 1133 * @return MHD result code 1134 */ 1135 static enum MHD_Result 1136 reply_orders (struct TMH_PendingOrder *po, 1137 struct MHD_Connection *connection, 1138 struct TMH_MerchantInstance *mi) 1139 { 1140 char total_buf[128]; 1141 char refund_buf[128]; 1142 char pending_buf[128]; 1143 1144 switch (po->format) 1145 { 1146 case POF_JSON: 1147 return TALER_MHD_REPLY_JSON_PACK ( 1148 connection, 1149 MHD_HTTP_OK, 1150 GNUNET_JSON_pack_array_incref ("orders", 1151 po->pa)); 1152 case POF_CSV: 1153 { 1154 struct MHD_Response *resp; 1155 enum MHD_Result mret; 1156 1157 for (unsigned int i = 0; i<po->total_amount.taa_size; i++) 1158 { 1159 struct TALER_Amount *tai = &po->total_amount.taa[i]; 1160 const struct TALER_Amount *r; 1161 1162 strcpy (total_buf, 1163 TALER_amount2s (tai)); 1164 r = TALER_amount_set_find (tai->currency, 1165 &po->total_refund_amount); 1166 strcpy (refund_buf, 1167 TALER_amount2s (r)); 1168 r = TALER_amount_set_find (tai->currency, 1169 &po->total_pending_refund_amount); 1170 strcpy (pending_buf, 1171 TALER_amount2s (r)); 1172 1173 GNUNET_buffer_write_fstr ( 1174 &po->csv, 1175 "Total (paid %s only),,,,%s,%s,%s,,,\r\n", 1176 tai->currency, 1177 total_buf, 1178 refund_buf, 1179 pending_buf); 1180 } 1181 GNUNET_buffer_write_str (&po->csv, 1182 CSV_FOOTER); 1183 resp = MHD_create_response_from_buffer (po->csv.position, 1184 po->csv.mem, 1185 MHD_RESPMEM_MUST_COPY); 1186 TALER_MHD_add_global_headers (resp, 1187 false); 1188 GNUNET_break (MHD_YES == 1189 MHD_add_response_header (resp, 1190 MHD_HTTP_HEADER_CONTENT_TYPE, 1191 "text/csv")); 1192 mret = MHD_queue_response (connection, 1193 MHD_HTTP_OK, 1194 resp); 1195 MHD_destroy_response (resp); 1196 return mret; 1197 } 1198 case POF_XML: 1199 { 1200 struct MHD_Response *resp; 1201 enum MHD_Result mret; 1202 1203 for (unsigned int i = 0; i<po->total_amount.taa_size; i++) 1204 { 1205 struct TALER_Amount *tai = &po->total_amount.taa[i]; 1206 const struct TALER_Amount *r; 1207 1208 strcpy (total_buf, 1209 TALER_amount2s (tai)); 1210 r = TALER_amount_set_find (tai->currency, 1211 &po->total_refund_amount); 1212 strcpy (refund_buf, 1213 TALER_amount2s (r)); 1214 r = TALER_amount_set_find (tai->currency, 1215 &po->total_pending_refund_amount); 1216 strcpy (pending_buf, 1217 TALER_amount2s (r)); 1218 1219 /* Append totals row with paid and refunded amount columns */ 1220 GNUNET_buffer_write_fstr ( 1221 &po->xml, 1222 "<Row>" 1223 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">Total (paid %s only)</Data></Cell>" 1224 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>" 1225 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>" 1226 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>" 1227 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>" 1228 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>" 1229 "</Row>\n", 1230 tai->currency, 1231 total_buf, 1232 refund_buf); 1233 } 1234 GNUNET_buffer_write_str (&po->xml, 1235 XML_FOOTER); 1236 resp = MHD_create_response_from_buffer (po->xml.position, 1237 po->xml.mem, 1238 MHD_RESPMEM_MUST_COPY); 1239 TALER_MHD_add_global_headers (resp, 1240 false); 1241 GNUNET_break (MHD_YES == 1242 MHD_add_response_header (resp, 1243 MHD_HTTP_HEADER_CONTENT_TYPE, 1244 "application/vnd.ms-excel")); 1245 mret = MHD_queue_response (connection, 1246 MHD_HTTP_OK, 1247 resp); 1248 MHD_destroy_response (resp); 1249 return mret; 1250 } 1251 case POF_PDF: 1252 { 1253 /* Build the JSON document for Typst, passing all totals */ 1254 json_t *root; 1255 struct TALER_MHD_TypstDocument doc; 1256 json_t *ta = json_array (); 1257 json_t *ra = json_array (); 1258 json_t *pa = json_array (); 1259 1260 GNUNET_assert (NULL != ta); 1261 GNUNET_assert (NULL != ra); 1262 GNUNET_assert (NULL != pa); 1263 for (unsigned int i = 0; i<po->total_amount.taa_size; i++) 1264 { 1265 struct TALER_Amount *tai = &po->total_amount.taa[i]; 1266 const struct TALER_Amount *r; 1267 1268 GNUNET_assert (0 == 1269 json_array_append_new (ta, 1270 TALER_JSON_from_amount (tai))); 1271 r = TALER_amount_set_find (tai->currency, 1272 &po->total_refund_amount); 1273 GNUNET_assert (0 == 1274 json_array_append_new (ra, 1275 TALER_JSON_from_amount (r))); 1276 r = TALER_amount_set_find (tai->currency, 1277 &po->total_pending_refund_amount); 1278 GNUNET_assert (0 == 1279 json_array_append_new (pa, 1280 TALER_JSON_from_amount (r))); 1281 } 1282 root = GNUNET_JSON_PACK ( 1283 GNUNET_JSON_pack_string ("business_name", 1284 mi->settings.name), 1285 GNUNET_JSON_pack_array_incref ("orders", 1286 po->pa), 1287 GNUNET_JSON_pack_array_steal ("total_amounts", 1288 ta), 1289 GNUNET_JSON_pack_array_steal ("total_refund_amounts", 1290 ra), 1291 GNUNET_JSON_pack_array_steal ("total_pending_refund_amounts", 1292 pa)); 1293 doc.form_name = "orders"; 1294 doc.form_version = "0.0.0"; 1295 doc.data = root; 1296 1297 po->tc = TALER_MHD_typst (TALER_MERCHANT_project_data (), 1298 TMH_cfg, 1299 false, /* remove on exit */ 1300 "merchant", 1301 1, /* one document */ 1302 &doc, 1303 &pdf_cb, 1304 po); 1305 json_decref (root); 1306 if (NULL == po->tc) 1307 { 1308 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1309 "Client requested PDF, but Typst is unavailable\n"); 1310 return TALER_MHD_reply_with_error ( 1311 connection, 1312 MHD_HTTP_NOT_IMPLEMENTED, 1313 TALER_EC_EXCHANGE_GENERIC_NO_TYPST_OR_PDFTK, 1314 NULL); 1315 } 1316 GNUNET_CONTAINER_DLL_insert (pdf_head, 1317 pdf_tail, 1318 po); 1319 MHD_suspend_connection (connection); 1320 return MHD_YES; 1321 } 1322 } /* end switch */ 1323 GNUNET_assert (0); 1324 return MHD_NO; 1325 } 1326 1327 1328 /** 1329 * Handle a GET "/orders" request. 1330 * 1331 * @param rh context of the handler 1332 * @param connection the MHD connection to handle 1333 * @param[in,out] hc context with further information about the request 1334 * @return MHD result code 1335 */ 1336 enum MHD_Result 1337 TMH_private_get_orders (const struct TMH_RequestHandler *rh, 1338 struct MHD_Connection *connection, 1339 struct TMH_HandlerContext *hc) 1340 { 1341 struct TMH_PendingOrder *po = hc->ctx; 1342 struct TMH_MerchantInstance *mi = hc->instance; 1343 enum GNUNET_DB_QueryStatus qs; 1344 1345 if (NULL != po) 1346 { 1347 if (po->recheck) 1348 { 1349 if (NULL != po->order_timeout_task) 1350 { 1351 GNUNET_SCHEDULER_cancel (po->order_timeout_task); 1352 po->order_timeout_task = NULL; 1353 } 1354 po->recheck = false; 1355 goto run_query; 1356 } 1357 if (TALER_EC_NONE != po->result) 1358 { 1359 /* Resumed from long-polling with error */ 1360 GNUNET_break (0); 1361 return TALER_MHD_reply_with_error (connection, 1362 MHD_HTTP_INTERNAL_SERVER_ERROR, 1363 po->result, 1364 NULL); 1365 } 1366 if (POF_PDF == po->format) 1367 { 1368 /* resumed from long-polling or from Typst PDF generation */ 1369 /* We really must have a response in this case */ 1370 if (NULL == po->response) 1371 { 1372 GNUNET_break (0); 1373 return MHD_NO; 1374 } 1375 return MHD_queue_response (connection, 1376 po->http_status, 1377 po->response); 1378 } 1379 return reply_orders (po, 1380 connection, 1381 mi); 1382 } 1383 po = GNUNET_new (struct TMH_PendingOrder); 1384 hc->ctx = po; 1385 hc->cc = &cleanup; 1386 po->con = connection; 1387 po->pa = json_array (); 1388 GNUNET_assert (NULL != po->pa); 1389 po->instance_id = mi->settings.id; 1390 po->mi = mi; 1391 1392 /* Determine desired output format from Accept header */ 1393 { 1394 const char *mime; 1395 1396 mime = MHD_lookup_connection_value (connection, 1397 MHD_HEADER_KIND, 1398 MHD_HTTP_HEADER_ACCEPT); 1399 if (NULL == mime) 1400 mime = "application/json"; 1401 if (0 == strcmp (mime, 1402 "*/*")) 1403 mime = "application/json"; 1404 if (0 == strcmp (mime, 1405 "application/json")) 1406 { 1407 po->format = POF_JSON; 1408 } 1409 else if (0 == strcmp (mime, 1410 "text/csv")) 1411 { 1412 po->format = POF_CSV; 1413 GNUNET_buffer_write_str (&po->csv, 1414 CSV_HEADER); 1415 } 1416 else if (0 == strcmp (mime, 1417 "application/vnd.ms-excel")) 1418 { 1419 po->format = POF_XML; 1420 GNUNET_buffer_write_str (&po->xml, 1421 XML_HEADER); 1422 } 1423 else if (0 == strcmp (mime, 1424 "application/pdf")) 1425 { 1426 po->format = POF_PDF; 1427 } 1428 else 1429 { 1430 GNUNET_break_op (0); 1431 return TALER_MHD_REPLY_JSON_PACK ( 1432 connection, 1433 MHD_HTTP_NOT_ACCEPTABLE, 1434 GNUNET_JSON_pack_string ("hint", 1435 mime)); 1436 } 1437 } 1438 1439 if (! (TALER_MHD_arg_to_yna (connection, 1440 "paid", 1441 TALER_EXCHANGE_YNA_ALL, 1442 &po->of.paid)) ) 1443 { 1444 GNUNET_break_op (0); 1445 return TALER_MHD_reply_with_error (connection, 1446 MHD_HTTP_BAD_REQUEST, 1447 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1448 "paid"); 1449 } 1450 if (! (TALER_MHD_arg_to_yna (connection, 1451 "refunded", 1452 TALER_EXCHANGE_YNA_ALL, 1453 &po->of.refunded)) ) 1454 { 1455 GNUNET_break_op (0); 1456 return TALER_MHD_reply_with_error (connection, 1457 MHD_HTTP_BAD_REQUEST, 1458 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1459 "refunded"); 1460 } 1461 if (! (TALER_MHD_arg_to_yna (connection, 1462 "wired", 1463 TALER_EXCHANGE_YNA_ALL, 1464 &po->of.wired)) ) 1465 { 1466 GNUNET_break_op (0); 1467 return TALER_MHD_reply_with_error (connection, 1468 MHD_HTTP_BAD_REQUEST, 1469 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1470 "wired"); 1471 } 1472 if (! (TALER_MHD_arg_to_yna (connection, 1473 "expired", 1474 TALER_EXCHANGE_YNA_ALL, 1475 &po->of.expired)) ) 1476 { 1477 GNUNET_break_op (0); 1478 return TALER_MHD_reply_with_error (connection, 1479 MHD_HTTP_BAD_REQUEST, 1480 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1481 "expired"); 1482 } 1483 po->of.delta = -20; 1484 /* deprecated in protocol v12 */ 1485 TALER_MHD_parse_request_snumber (connection, 1486 "delta", 1487 &po->of.delta); 1488 /* since protocol v12 */ 1489 TALER_MHD_parse_request_snumber (connection, 1490 "limit", 1491 &po->of.delta); 1492 if ( (-MAX_DELTA > po->of.delta) || 1493 (po->of.delta > MAX_DELTA) ) 1494 { 1495 GNUNET_break_op (0); 1496 return TALER_MHD_reply_with_error (connection, 1497 MHD_HTTP_BAD_REQUEST, 1498 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1499 "limit"); 1500 } 1501 { 1502 const char *date_s_str; 1503 1504 date_s_str = MHD_lookup_connection_value (connection, 1505 MHD_GET_ARGUMENT_KIND, 1506 "date_s"); 1507 if (NULL == date_s_str) 1508 { 1509 if (po->of.delta > 0) 1510 po->of.date = GNUNET_TIME_UNIT_ZERO_TS; 1511 else 1512 po->of.date = GNUNET_TIME_UNIT_FOREVER_TS; 1513 } 1514 else 1515 { 1516 char dummy; 1517 unsigned long long ll; 1518 1519 if (1 != 1520 sscanf (date_s_str, 1521 "%llu%c", 1522 &ll, 1523 &dummy)) 1524 { 1525 GNUNET_break_op (0); 1526 return TALER_MHD_reply_with_error (connection, 1527 MHD_HTTP_BAD_REQUEST, 1528 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1529 "date_s"); 1530 } 1531 1532 po->of.date = GNUNET_TIME_absolute_to_timestamp ( 1533 GNUNET_TIME_absolute_from_s (ll)); 1534 if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time)) 1535 { 1536 GNUNET_break_op (0); 1537 return TALER_MHD_reply_with_error (connection, 1538 MHD_HTTP_BAD_REQUEST, 1539 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1540 "date_s"); 1541 } 1542 } 1543 } 1544 if (po->of.delta > 0) 1545 { 1546 /* Only narrow the range if the client actually asked for it: 1547 the default duration of zero would cut off everything 1548 created before *now*. */ 1549 bool have_max_age 1550 = (NULL != 1551 MHD_lookup_connection_value (connection, 1552 MHD_GET_ARGUMENT_KIND, 1553 "max_age")); 1554 bool have_max_age_s 1555 = (NULL != 1556 MHD_lookup_connection_value (connection, 1557 MHD_GET_ARGUMENT_KIND, 1558 "max_age_s")); 1559 struct GNUNET_TIME_Relative duration = GNUNET_TIME_UNIT_ZERO; 1560 1561 if (have_max_age) 1562 { 1563 /* deprecated in protocol v33, superseded by "max_age_s" */ 1564 TALER_MHD_parse_request_rel_time (connection, 1565 "max_age", 1566 &duration); 1567 } 1568 if (have_max_age_s) 1569 { 1570 /* since protocol v33 */ 1571 uint64_t max_age_s = 0; 1572 struct GNUNET_TIME_Relative age; 1573 1574 TALER_MHD_parse_request_number (connection, 1575 "max_age_s", 1576 &max_age_s); 1577 age = GNUNET_TIME_relative_saturating_multiply (GNUNET_TIME_UNIT_SECONDS, 1578 max_age_s); 1579 if (have_max_age && 1580 GNUNET_TIME_relative_cmp (duration, 1581 !=, 1582 age)) 1583 { 1584 unsigned long long max_age_ms 1585 = duration.rel_value_us 1586 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; 1587 1588 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1589 "Client gave conflicting values for `max_age' (%llu ms) and `max_age_s' (%llu s); using `max_age_s'\n", 1590 max_age_ms, 1591 (unsigned long long) max_age_s); 1592 } 1593 duration = age; 1594 } 1595 if (have_max_age || have_max_age_s) 1596 { 1597 struct GNUNET_TIME_Absolute cut_off; 1598 1599 cut_off = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (), 1600 duration); 1601 po->of.date = GNUNET_TIME_timestamp_max ( 1602 po->of.date, 1603 GNUNET_TIME_absolute_to_timestamp (cut_off)); 1604 } 1605 } 1606 if (po->of.delta > 0) 1607 po->of.start_row = 0; 1608 else 1609 po->of.start_row = INT64_MAX; 1610 /* deprecated in protocol v12 */ 1611 TALER_MHD_parse_request_number (connection, 1612 "start", 1613 &po->of.start_row); 1614 /* since protocol v12 */ 1615 TALER_MHD_parse_request_number (connection, 1616 "offset", 1617 &po->of.start_row); 1618 if (INT64_MAX < po->of.start_row) 1619 { 1620 GNUNET_break_op (0); 1621 return TALER_MHD_reply_with_error (connection, 1622 MHD_HTTP_BAD_REQUEST, 1623 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1624 "offset"); 1625 } 1626 po->summary_filter = tr (MHD_lookup_connection_value (connection, 1627 MHD_GET_ARGUMENT_KIND, 1628 "summary_filter")); 1629 po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */ 1630 po->of.session_id 1631 = MHD_lookup_connection_value (connection, 1632 MHD_GET_ARGUMENT_KIND, 1633 "session_id"); 1634 po->of.fulfillment_url 1635 = MHD_lookup_connection_value (connection, 1636 MHD_GET_ARGUMENT_KIND, 1637 "fulfillment_url"); 1638 TALER_MHD_parse_request_timeout (connection, 1639 &po->long_poll_timeout); 1640 if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout)) 1641 { 1642 GNUNET_break_op (0); 1643 return TALER_MHD_reply_with_error (connection, 1644 MHD_HTTP_BAD_REQUEST, 1645 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1646 "timeout_ms"); 1647 } 1648 if ( (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) && 1649 (NULL == mi->po_eh) ) 1650 { 1651 struct TMH_OrderChangeEventP change_eh = { 1652 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE), 1653 .header.size = htons (sizeof (change_eh)), 1654 .merchant_pub = mi->merchant_pub 1655 }; 1656 1657 mi->po_eh = TALER_MERCHANTDB_event_listen (TMH_db, 1658 &change_eh.header, 1659 GNUNET_TIME_UNIT_FOREVER_REL, 1660 &resume_by_event, 1661 mi); 1662 } 1663 1664 run_query: 1665 po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout); 1666 1667 qs = TALER_MERCHANTDB_iterate_orders (TMH_db, 1668 po->instance_id, 1669 &po->of, 1670 &add_order, 1671 po); 1672 if (0 > qs) 1673 { 1674 GNUNET_break (0); 1675 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 1676 } 1677 if (TALER_EC_NONE != po->result) 1678 { 1679 GNUNET_break (0); 1680 return TALER_MHD_reply_with_error (connection, 1681 MHD_HTTP_INTERNAL_SERVER_ERROR, 1682 po->result, 1683 NULL); 1684 } 1685 if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) && 1686 (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) ) 1687 { 1688 struct GNUNET_TIME_Absolute wakeup = po->long_poll_timeout; 1689 1690 if (TALER_EXCHANGE_YNA_YES == po->of.expired) 1691 wakeup = GNUNET_TIME_absolute_min ( 1692 wakeup, 1693 GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_SECONDS)); 1694 GNUNET_assert (NULL == po->order_timeout_task); 1695 po->order_timeout_task 1696 = GNUNET_SCHEDULER_add_at (wakeup, 1697 &order_timeout, 1698 po); 1699 GNUNET_CONTAINER_DLL_insert (mi->po_head, 1700 mi->po_tail, 1701 po); 1702 po->in_dll = true; 1703 MHD_suspend_connection (connection); 1704 return MHD_YES; 1705 } 1706 return reply_orders (po, 1707 connection, 1708 mi); 1709 } 1710 1711 1712 /* end of taler-merchant-httpd_get-private-orders.c */