taler-merchant-httpd_get-private-orders.c (48455B)
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 default: 676 GNUNET_break (0); 677 po->result = TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION; 678 goto cleanup; 679 } 680 681 /* convert amounts to strings (needed for some formats) */ 682 /* FIXME: use currency formatting rules in the future 683 instead of TALER_amount2s for human readability... */ 684 strcpy (amount_buf, 685 TALER_amount2s (amount)); 686 if (paid) 687 strcpy (refund_buf, 688 TALER_amount2s (&prc.total_refund_amount)); 689 if (paid) 690 strcpy (pending_buf, 691 TALER_amount2s (&prc.pending_refund_amount)); 692 693 switch (po->format) 694 { 695 case POF_JSON: 696 case POF_PDF: 697 GNUNET_assert ( 698 0 == 699 json_array_append_new ( 700 po->pa, 701 GNUNET_JSON_PACK ( 702 GNUNET_JSON_pack_string ("order_id", 703 contract->order_id), 704 GNUNET_JSON_pack_uint64 ("row_id", 705 order_serial), 706 GNUNET_JSON_pack_timestamp ("timestamp", 707 creation_time), 708 TALER_JSON_pack_amount ("amount", 709 amount), 710 GNUNET_JSON_pack_allow_null ( 711 TALER_JSON_pack_amount ( 712 "refund_amount", 713 paid 714 ? &prc.total_refund_amount 715 : NULL)), 716 GNUNET_JSON_pack_allow_null ( 717 TALER_JSON_pack_amount ( 718 "pending_refund_amount", 719 paid 720 ? &prc.pending_refund_amount 721 : NULL)), 722 GNUNET_JSON_pack_string ("summary", 723 contract->summary), 724 GNUNET_JSON_pack_bool ("refundable", 725 refundable), 726 GNUNET_JSON_pack_bool ("paid", 727 paid)))); 728 break; 729 case POF_CSV: 730 { 731 size_t len = strlen (contract->summary); 732 size_t wpos = 0; 733 char *esummary; 734 struct tm *tm; 735 time_t t; 736 737 /* Escape 'summary' to double '"' as per RFC 4180, 2.7. */ 738 esummary = GNUNET_malloc (2 * len + 1); 739 for (size_t off = 0; off<len; off++) 740 { 741 if ('"' == contract->summary[off]) 742 esummary[wpos++] = '"'; 743 esummary[wpos++] = contract->summary[off]; 744 } 745 t = GNUNET_TIME_timestamp_to_s (creation_time); 746 tm = localtime (&t); 747 GNUNET_buffer_write_fstr ( 748 &po->csv, 749 "%s,%llu,%04u-%02u-%02u,%02u:%02u (%s),%llu,%s,%s,%s,\"%s\",%s,%s\r\n", 750 contract->order_id, 751 (unsigned long long) order_serial, 752 tm->tm_year + 1900, 753 tm->tm_mon + 1, 754 tm->tm_mday, 755 tm->tm_hour, 756 tm->tm_min, 757 tm->tm_zone, 758 (unsigned long long) t, 759 amount_buf, 760 paid ? refund_buf : "", 761 paid ? pending_buf : "", 762 esummary, 763 refundable ? "yes" : "no", 764 paid ? "yes" : "no"); 765 GNUNET_free (esummary); 766 break; 767 } 768 case POF_XML: 769 { 770 char *esummary = TALER_escape_xml (contract->summary); 771 char creation_time_s[128]; 772 const struct tm *tm; 773 time_t tt; 774 775 tt = (time_t) GNUNET_TIME_timestamp_to_s (creation_time); 776 tm = gmtime (&tt); 777 strftime (creation_time_s, 778 sizeof (creation_time_s), 779 "%Y-%m-%dT%H:%M:%S", 780 tm); 781 GNUNET_buffer_write_fstr ( 782 &po->xml, 783 "<Row>" 784 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 785 "<Cell ss:StyleID=\"DateFormat\"><Data ss:Type=\"DateTime\">%s</Data></Cell>" 786 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 787 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 788 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 789 "<Cell ss:Formula=\"=%s()\"><Data ss:Type=\"Boolean\">%s</Data></Cell>" 790 "</Row>\n", 791 contract->order_id, 792 creation_time_s, 793 amount_buf, 794 paid ? refund_buf : "", 795 NULL != esummary ? esummary : "", 796 paid ? "TRUE" : "FALSE", 797 paid ? "1" : "0"); 798 GNUNET_free (esummary); 799 } 800 break; 801 } /* end switch po->format */ 802 803 cleanup: 804 json_decref (contract_terms); 805 GNUNET_free (order_id); 806 if (NULL != contract) 807 { 808 TALER_MERCHANT_contract_free (contract); 809 contract = NULL; 810 } 811 } 812 813 814 /** 815 * We have received a trigger from the database 816 * that we should (possibly) resume some requests. 817 * 818 * @param cls a `struct TMH_MerchantInstance` 819 * @param extra a `struct TMH_OrderChangeEventP` 820 * @param extra_size number of bytes in @a extra 821 */ 822 static void 823 resume_by_event (void *cls, 824 const void *extra, 825 size_t extra_size) 826 { 827 struct TMH_MerchantInstance *mi = cls; 828 const struct TMH_OrderChangeEventDetailsP *oce = extra; 829 struct TMH_PendingOrder *pn; 830 enum TMH_OrderStateFlags osf; 831 uint64_t order_serial_id; 832 struct GNUNET_TIME_Timestamp date; 833 834 if (sizeof (*oce) != extra_size) 835 { 836 GNUNET_break (0); 837 return; 838 } 839 osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state); 840 order_serial_id = GNUNET_ntohll (oce->order_serial_id); 841 date = GNUNET_TIME_timestamp_ntoh (oce->execution_date); 842 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 843 "Received notification about order %llu\n", 844 (unsigned long long) order_serial_id); 845 for (struct TMH_PendingOrder *po = mi->po_head; 846 NULL != po; 847 po = pn) 848 { 849 pn = po->next; 850 if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) == 851 (0 != (osf & TMH_OSF_PAID))) || 852 (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) && 853 ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) == 854 (0 != (osf & TMH_OSF_REFUNDED))) || 855 (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) && 856 ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) == 857 (0 != (osf & TMH_OSF_WIRED))) || 858 (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) ) 859 { 860 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 861 "Client %p waits on different order type\n", 862 po); 863 continue; 864 } 865 if (po->of.delta > 0) 866 { 867 if (order_serial_id < po->of.start_row) 868 { 869 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 870 "Client %p waits on different order row\n", 871 po); 872 continue; 873 } 874 if (GNUNET_TIME_timestamp_cmp (date, 875 <, 876 po->of.date)) 877 { 878 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 879 "Client %p waits on different order date\n", 880 po); 881 continue; 882 } 883 po->of.delta--; 884 } 885 else 886 { 887 if (order_serial_id > po->of.start_row) 888 { 889 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 890 "Client %p waits on different order row\n", 891 po); 892 continue; 893 } 894 if (GNUNET_TIME_timestamp_cmp (date, 895 >, 896 po->of.date)) 897 { 898 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 899 "Client %p waits on different order date\n", 900 po); 901 continue; 902 } 903 po->of.delta++; 904 } 905 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 906 "Waking up client %p!\n", 907 po); 908 add_order (po, 909 NULL, 910 order_serial_id, 911 date); 912 GNUNET_assert (po->in_dll); 913 GNUNET_CONTAINER_DLL_remove (mi->po_head, 914 mi->po_tail, 915 po); 916 po->in_dll = false; 917 MHD_resume_connection (po->con); 918 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 919 } 920 if (NULL == mi->po_head) 921 { 922 TMH_db->event_listen_cancel (mi->po_eh); 923 mi->po_eh = NULL; 924 } 925 } 926 927 928 /** 929 * There has been a change or addition of a new @a order_id. Wake up 930 * long-polling clients that may have been waiting for this event. 931 * 932 * @param mi the instance where the order changed 933 * @param osf order state flags 934 * @param date execution date of the order 935 * @param order_serial_id serial ID of the order in the database 936 */ 937 void 938 TMH_notify_order_change (struct TMH_MerchantInstance *mi, 939 enum TMH_OrderStateFlags osf, 940 struct GNUNET_TIME_Timestamp date, 941 uint64_t order_serial_id) 942 { 943 struct TMH_OrderChangeEventDetailsP oce = { 944 .order_serial_id = GNUNET_htonll (order_serial_id), 945 .execution_date = GNUNET_TIME_timestamp_hton (date), 946 .order_state = htonl (osf) 947 }; 948 struct TMH_OrderChangeEventP eh = { 949 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE), 950 .header.size = htons (sizeof (eh)), 951 .merchant_pub = mi->merchant_pub 952 }; 953 954 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 955 "Notifying clients of new order %llu at %s\n", 956 (unsigned long long) order_serial_id, 957 TALER_B2S (&mi->merchant_pub)); 958 TMH_db->event_notify (TMH_db->cls, 959 &eh.header, 960 &oce, 961 sizeof (oce)); 962 } 963 964 965 /** 966 * Transforms an (untrusted) input filter into a Postgresql LIKE filter. 967 * Escapes "%" and "_" in the @a input and adds "%" at the beginning 968 * and the end to turn the @a input into a suitable Postgresql argument. 969 * 970 * @param input text to turn into a substring match expression, or NULL 971 * @return NULL if @a input was NULL, otherwise transformed @a input 972 */ 973 static char * 974 tr (const char *input) 975 { 976 char *out; 977 size_t slen; 978 size_t wpos; 979 980 if (NULL == input) 981 return NULL; 982 slen = strlen (input); 983 out = GNUNET_malloc (slen * 2 + 3); 984 wpos = 0; 985 out[wpos++] = '%'; 986 for (size_t i = 0; i<slen; i++) 987 { 988 char c = input[i]; 989 990 if ( (c == '%') || 991 (c == '_') ) 992 out[wpos++] = '\\'; 993 out[wpos++] = c; 994 } 995 out[wpos++] = '%'; 996 GNUNET_assert (wpos < slen * 2 + 3); 997 return out; 998 } 999 1000 1001 /** 1002 * Function called with the result of a #TALER_MHD_typst() operation. 1003 * 1004 * @param cls closure, a `struct TMH_PendingOrder *` 1005 * @param tr result of the operation 1006 */ 1007 static void 1008 pdf_cb (void *cls, 1009 const struct TALER_MHD_TypstResponse *tr) 1010 { 1011 struct TMH_PendingOrder *po = cls; 1012 1013 po->tc = NULL; 1014 GNUNET_CONTAINER_DLL_remove (pdf_head, 1015 pdf_tail, 1016 po); 1017 if (TALER_EC_NONE != tr->ec) 1018 { 1019 po->http_status 1020 = TALER_ErrorCode_get_http_status (tr->ec); 1021 po->response 1022 = TALER_MHD_make_error (tr->ec, 1023 tr->details.hint); 1024 } 1025 else 1026 { 1027 po->http_status = MHD_HTTP_OK; 1028 po->response = TALER_MHD_response_from_pdf_file (tr->details.filename); 1029 } 1030 MHD_resume_connection (po->con); 1031 TALER_MHD_daemon_trigger (); 1032 } 1033 1034 1035 /** 1036 * Build the final response for a completed (non-long-poll) request and 1037 * queue it on @a connection. 1038 * 1039 * Handles all formats (JSON, CSV, XML, PDF). For PDF this may suspend 1040 * the connection while Typst runs asynchronously; in that case the caller 1041 * must return #MHD_YES immediately. 1042 * 1043 * @param po the pending order state (already fully populated) 1044 * @param connection the MHD connection 1045 * @param mi the merchant instance 1046 * @return MHD result code 1047 */ 1048 static MHD_RESULT 1049 reply_orders (struct TMH_PendingOrder *po, 1050 struct MHD_Connection *connection, 1051 struct TMH_MerchantInstance *mi) 1052 { 1053 char total_buf[128]; 1054 char refund_buf[128]; 1055 char pending_buf[128]; 1056 1057 switch (po->format) 1058 { 1059 case POF_JSON: 1060 return TALER_MHD_REPLY_JSON_PACK ( 1061 connection, 1062 MHD_HTTP_OK, 1063 GNUNET_JSON_pack_array_incref ("orders", 1064 po->pa)); 1065 case POF_CSV: 1066 { 1067 struct MHD_Response *resp; 1068 MHD_RESULT mret; 1069 1070 for (unsigned int i = 0; i<po->total_amount.taa_size; i++) 1071 { 1072 struct TALER_Amount *tai = &po->total_amount.taa[i]; 1073 const struct TALER_Amount *r; 1074 1075 strcpy (total_buf, 1076 TALER_amount2s (tai)); 1077 r = TALER_amount_set_find (tai->currency, 1078 &po->total_refund_amount); 1079 strcpy (refund_buf, 1080 TALER_amount2s (r)); 1081 r = TALER_amount_set_find (tai->currency, 1082 &po->total_pending_refund_amount); 1083 strcpy (pending_buf, 1084 TALER_amount2s (r)); 1085 1086 GNUNET_buffer_write_fstr ( 1087 &po->csv, 1088 "Total (paid %s only),,,,%s,%s,%s,,,\r\n", 1089 tai->currency, 1090 total_buf, 1091 refund_buf, 1092 pending_buf); 1093 } 1094 GNUNET_buffer_write_str (&po->csv, 1095 CSV_FOOTER); 1096 resp = MHD_create_response_from_buffer (po->csv.position, 1097 po->csv.mem, 1098 MHD_RESPMEM_MUST_COPY); 1099 TALER_MHD_add_global_headers (resp, 1100 false); 1101 GNUNET_break (MHD_YES == 1102 MHD_add_response_header (resp, 1103 MHD_HTTP_HEADER_CONTENT_TYPE, 1104 "text/csv")); 1105 mret = MHD_queue_response (connection, 1106 MHD_HTTP_OK, 1107 resp); 1108 MHD_destroy_response (resp); 1109 return mret; 1110 } 1111 case POF_XML: 1112 { 1113 struct MHD_Response *resp; 1114 MHD_RESULT mret; 1115 1116 for (unsigned int i = 0; i<po->total_amount.taa_size; i++) 1117 { 1118 struct TALER_Amount *tai = &po->total_amount.taa[i]; 1119 const struct TALER_Amount *r; 1120 1121 strcpy (total_buf, 1122 TALER_amount2s (tai)); 1123 r = TALER_amount_set_find (tai->currency, 1124 &po->total_refund_amount); 1125 strcpy (refund_buf, 1126 TALER_amount2s (r)); 1127 r = TALER_amount_set_find (tai->currency, 1128 &po->total_pending_refund_amount); 1129 strcpy (pending_buf, 1130 TALER_amount2s (r)); 1131 1132 /* Append totals row with paid and refunded amount columns */ 1133 GNUNET_buffer_write_fstr ( 1134 &po->xml, 1135 "<Row>" 1136 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">Total (paid %s only)</Data></Cell>" 1137 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>" 1138 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>" 1139 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>" 1140 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>" 1141 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>" 1142 "</Row>\n", 1143 tai->currency, 1144 total_buf, 1145 refund_buf); 1146 } 1147 GNUNET_buffer_write_str (&po->xml, 1148 XML_FOOTER); 1149 resp = MHD_create_response_from_buffer (po->xml.position, 1150 po->xml.mem, 1151 MHD_RESPMEM_MUST_COPY); 1152 TALER_MHD_add_global_headers (resp, 1153 false); 1154 GNUNET_break (MHD_YES == 1155 MHD_add_response_header (resp, 1156 MHD_HTTP_HEADER_CONTENT_TYPE, 1157 "application/vnd.ms-excel")); 1158 mret = MHD_queue_response (connection, 1159 MHD_HTTP_OK, 1160 resp); 1161 MHD_destroy_response (resp); 1162 return mret; 1163 } 1164 case POF_PDF: 1165 { 1166 /* Build the JSON document for Typst, passing all totals */ 1167 json_t *root; 1168 struct TALER_MHD_TypstDocument doc; 1169 json_t *ta = json_array (); 1170 json_t *ra = json_array (); 1171 json_t *pa = json_array (); 1172 1173 GNUNET_assert (NULL != ta); 1174 GNUNET_assert (NULL != ra); 1175 GNUNET_assert (NULL != pa); 1176 for (unsigned int i = 0; i<po->total_amount.taa_size; i++) 1177 { 1178 struct TALER_Amount *tai = &po->total_amount.taa[i]; 1179 const struct TALER_Amount *r; 1180 1181 GNUNET_assert (0 == 1182 json_array_append_new (ta, 1183 TALER_JSON_from_amount (tai))); 1184 r = TALER_amount_set_find (tai->currency, 1185 &po->total_refund_amount); 1186 GNUNET_assert (0 == 1187 json_array_append_new (ra, 1188 TALER_JSON_from_amount (r))); 1189 r = TALER_amount_set_find (tai->currency, 1190 &po->total_pending_refund_amount); 1191 GNUNET_assert (0 == 1192 json_array_append_new (pa, 1193 TALER_JSON_from_amount (r))); 1194 } 1195 root = GNUNET_JSON_PACK ( 1196 GNUNET_JSON_pack_string ("business_name", 1197 mi->settings.name), 1198 GNUNET_JSON_pack_array_incref ("orders", 1199 po->pa), 1200 GNUNET_JSON_pack_array_steal ("total_amounts", 1201 ta), 1202 GNUNET_JSON_pack_array_steal ("total_refund_amounts", 1203 ra), 1204 GNUNET_JSON_pack_array_steal ("total_pending_refund_amounts", 1205 pa)); 1206 doc.form_name = "orders"; 1207 doc.form_version = "0.0.0"; 1208 doc.data = root; 1209 1210 po->tc = TALER_MHD_typst (TMH_cfg, 1211 false, /* remove on exit */ 1212 "merchant", 1213 1, /* one document */ 1214 &doc, 1215 &pdf_cb, 1216 po); 1217 json_decref (root); 1218 if (NULL == po->tc) 1219 { 1220 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1221 "Client requested PDF, but Typst is unavailable\n"); 1222 return TALER_MHD_reply_with_error ( 1223 connection, 1224 MHD_HTTP_NOT_IMPLEMENTED, 1225 TALER_EC_EXCHANGE_GENERIC_NO_TYPST_OR_PDFTK, 1226 NULL); 1227 } 1228 GNUNET_CONTAINER_DLL_insert (pdf_head, 1229 pdf_tail, 1230 po); 1231 MHD_suspend_connection (connection); 1232 return MHD_YES; 1233 } 1234 } /* end switch */ 1235 GNUNET_assert (0); 1236 return MHD_NO; 1237 } 1238 1239 1240 /** 1241 * Handle a GET "/orders" request. 1242 * 1243 * @param rh context of the handler 1244 * @param connection the MHD connection to handle 1245 * @param[in,out] hc context with further information about the request 1246 * @return MHD result code 1247 */ 1248 MHD_RESULT 1249 TMH_private_get_orders (const struct TMH_RequestHandler *rh, 1250 struct MHD_Connection *connection, 1251 struct TMH_HandlerContext *hc) 1252 { 1253 struct TMH_PendingOrder *po = hc->ctx; 1254 struct TMH_MerchantInstance *mi = hc->instance; 1255 enum GNUNET_DB_QueryStatus qs; 1256 1257 if (NULL != po) 1258 { 1259 if (TALER_EC_NONE != po->result) 1260 { 1261 /* Resumed from long-polling with error */ 1262 GNUNET_break (0); 1263 return TALER_MHD_reply_with_error (connection, 1264 MHD_HTTP_INTERNAL_SERVER_ERROR, 1265 po->result, 1266 NULL); 1267 } 1268 if (POF_PDF == po->format) 1269 { 1270 /* resumed from long-polling or from Typst PDF generation */ 1271 /* We really must have a response in this case */ 1272 if (NULL == po->response) 1273 { 1274 GNUNET_break (0); 1275 return MHD_NO; 1276 } 1277 return MHD_queue_response (connection, 1278 po->http_status, 1279 po->response); 1280 } 1281 return reply_orders (po, 1282 connection, 1283 mi); 1284 } 1285 po = GNUNET_new (struct TMH_PendingOrder); 1286 hc->ctx = po; 1287 hc->cc = &cleanup; 1288 po->con = connection; 1289 po->pa = json_array (); 1290 GNUNET_assert (NULL != po->pa); 1291 po->instance_id = mi->settings.id; 1292 po->mi = mi; 1293 1294 /* Determine desired output format from Accept header */ 1295 { 1296 const char *mime; 1297 1298 mime = MHD_lookup_connection_value (connection, 1299 MHD_HEADER_KIND, 1300 MHD_HTTP_HEADER_ACCEPT); 1301 if (NULL == mime) 1302 mime = "application/json"; 1303 if (0 == strcmp (mime, 1304 "*/*")) 1305 mime = "application/json"; 1306 if (0 == strcmp (mime, 1307 "application/json")) 1308 { 1309 po->format = POF_JSON; 1310 } 1311 else if (0 == strcmp (mime, 1312 "text/csv")) 1313 { 1314 po->format = POF_CSV; 1315 GNUNET_buffer_write_str (&po->csv, 1316 CSV_HEADER); 1317 } 1318 else if (0 == strcmp (mime, 1319 "application/vnd.ms-excel")) 1320 { 1321 po->format = POF_XML; 1322 GNUNET_buffer_write_str (&po->xml, 1323 XML_HEADER); 1324 } 1325 else if (0 == strcmp (mime, 1326 "application/pdf")) 1327 { 1328 po->format = POF_PDF; 1329 } 1330 else 1331 { 1332 GNUNET_break_op (0); 1333 return TALER_MHD_REPLY_JSON_PACK ( 1334 connection, 1335 MHD_HTTP_NOT_ACCEPTABLE, 1336 GNUNET_JSON_pack_string ("hint", 1337 mime)); 1338 } 1339 } 1340 1341 if (! (TALER_MHD_arg_to_yna (connection, 1342 "paid", 1343 TALER_EXCHANGE_YNA_ALL, 1344 &po->of.paid)) ) 1345 { 1346 GNUNET_break_op (0); 1347 return TALER_MHD_reply_with_error (connection, 1348 MHD_HTTP_BAD_REQUEST, 1349 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1350 "paid"); 1351 } 1352 if (! (TALER_MHD_arg_to_yna (connection, 1353 "refunded", 1354 TALER_EXCHANGE_YNA_ALL, 1355 &po->of.refunded)) ) 1356 { 1357 GNUNET_break_op (0); 1358 return TALER_MHD_reply_with_error (connection, 1359 MHD_HTTP_BAD_REQUEST, 1360 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1361 "refunded"); 1362 } 1363 if (! (TALER_MHD_arg_to_yna (connection, 1364 "wired", 1365 TALER_EXCHANGE_YNA_ALL, 1366 &po->of.wired)) ) 1367 { 1368 GNUNET_break_op (0); 1369 return TALER_MHD_reply_with_error (connection, 1370 MHD_HTTP_BAD_REQUEST, 1371 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1372 "wired"); 1373 } 1374 po->of.delta = -20; 1375 /* deprecated in protocol v12 */ 1376 TALER_MHD_parse_request_snumber (connection, 1377 "delta", 1378 &po->of.delta); 1379 /* since protocol v12 */ 1380 TALER_MHD_parse_request_snumber (connection, 1381 "limit", 1382 &po->of.delta); 1383 if ( (-MAX_DELTA > po->of.delta) || 1384 (po->of.delta > MAX_DELTA) ) 1385 { 1386 GNUNET_break_op (0); 1387 return TALER_MHD_reply_with_error (connection, 1388 MHD_HTTP_BAD_REQUEST, 1389 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1390 "limit"); 1391 } 1392 { 1393 const char *date_s_str; 1394 1395 date_s_str = MHD_lookup_connection_value (connection, 1396 MHD_GET_ARGUMENT_KIND, 1397 "date_s"); 1398 if (NULL == date_s_str) 1399 { 1400 if (po->of.delta > 0) 1401 po->of.date = GNUNET_TIME_UNIT_ZERO_TS; 1402 else 1403 po->of.date = GNUNET_TIME_UNIT_FOREVER_TS; 1404 } 1405 else 1406 { 1407 char dummy; 1408 unsigned long long ll; 1409 1410 if (1 != 1411 sscanf (date_s_str, 1412 "%llu%c", 1413 &ll, 1414 &dummy)) 1415 { 1416 GNUNET_break_op (0); 1417 return TALER_MHD_reply_with_error (connection, 1418 MHD_HTTP_BAD_REQUEST, 1419 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1420 "date_s"); 1421 } 1422 1423 po->of.date = GNUNET_TIME_absolute_to_timestamp ( 1424 GNUNET_TIME_absolute_from_s (ll)); 1425 if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time)) 1426 { 1427 GNUNET_break_op (0); 1428 return TALER_MHD_reply_with_error (connection, 1429 MHD_HTTP_BAD_REQUEST, 1430 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1431 "date_s"); 1432 } 1433 } 1434 } 1435 if (po->of.delta > 0) 1436 { 1437 struct GNUNET_TIME_Relative duration 1438 = GNUNET_TIME_UNIT_FOREVER_REL; 1439 struct GNUNET_TIME_Absolute cut_off; 1440 1441 TALER_MHD_parse_request_rel_time (connection, 1442 "max_age", 1443 &duration); 1444 cut_off = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (), 1445 duration); 1446 po->of.date = GNUNET_TIME_timestamp_max ( 1447 po->of.date, 1448 GNUNET_TIME_absolute_to_timestamp (cut_off)); 1449 } 1450 if (po->of.delta > 0) 1451 po->of.start_row = 0; 1452 else 1453 po->of.start_row = INT64_MAX; 1454 /* deprecated in protocol v12 */ 1455 TALER_MHD_parse_request_number (connection, 1456 "start", 1457 &po->of.start_row); 1458 /* since protocol v12 */ 1459 TALER_MHD_parse_request_number (connection, 1460 "offset", 1461 &po->of.start_row); 1462 if (INT64_MAX < po->of.start_row) 1463 { 1464 GNUNET_break_op (0); 1465 return TALER_MHD_reply_with_error (connection, 1466 MHD_HTTP_BAD_REQUEST, 1467 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1468 "offset"); 1469 } 1470 po->summary_filter = tr (MHD_lookup_connection_value (connection, 1471 MHD_GET_ARGUMENT_KIND, 1472 "summary_filter")); 1473 po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */ 1474 po->of.session_id 1475 = MHD_lookup_connection_value (connection, 1476 MHD_GET_ARGUMENT_KIND, 1477 "session_id"); 1478 po->of.fulfillment_url 1479 = MHD_lookup_connection_value (connection, 1480 MHD_GET_ARGUMENT_KIND, 1481 "fulfillment_url"); 1482 TALER_MHD_parse_request_timeout (connection, 1483 &po->long_poll_timeout); 1484 if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout)) 1485 { 1486 GNUNET_break_op (0); 1487 return TALER_MHD_reply_with_error (connection, 1488 MHD_HTTP_BAD_REQUEST, 1489 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1490 "timeout_ms"); 1491 } 1492 if ( (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) && 1493 (NULL == mi->po_eh) ) 1494 { 1495 struct TMH_OrderChangeEventP change_eh = { 1496 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE), 1497 .header.size = htons (sizeof (change_eh)), 1498 .merchant_pub = mi->merchant_pub 1499 }; 1500 1501 mi->po_eh = TMH_db->event_listen (TMH_db->cls, 1502 &change_eh.header, 1503 GNUNET_TIME_UNIT_FOREVER_REL, 1504 &resume_by_event, 1505 mi); 1506 } 1507 1508 po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout); 1509 1510 qs = TMH_db->lookup_orders (TMH_db->cls, 1511 po->instance_id, 1512 &po->of, 1513 &add_order, 1514 po); 1515 if (0 > qs) 1516 { 1517 GNUNET_break (0); 1518 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 1519 } 1520 if (TALER_EC_NONE != po->result) 1521 { 1522 GNUNET_break (0); 1523 return TALER_MHD_reply_with_error (connection, 1524 MHD_HTTP_INTERNAL_SERVER_ERROR, 1525 po->result, 1526 NULL); 1527 } 1528 if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) && 1529 (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) ) 1530 { 1531 GNUNET_assert (NULL == po->order_timeout_task); 1532 po->order_timeout_task 1533 = GNUNET_SCHEDULER_add_at (po->long_poll_timeout, 1534 &order_timeout, 1535 po); 1536 GNUNET_CONTAINER_DLL_insert (mi->po_head, 1537 mi->po_tail, 1538 po); 1539 po->in_dll = true; 1540 MHD_suspend_connection (connection); 1541 return MHD_YES; 1542 } 1543 return reply_orders (po, 1544 connection, 1545 mi); 1546 } 1547 1548 1549 /* end of taler-merchant-httpd_get-private-orders.c */