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