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