taler-merchant-httpd_get-private-orders.c (50249B)
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 *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 struct TALER_MERCHANT_Order *order = NULL; 474 int16_t choice_index = -1; 475 struct ProcessRefundsClosure prc = { 476 .ec = TALER_EC_NONE 477 }; 478 const struct TALER_Amount *amount; 479 char amount_buf[128]; 480 char refund_buf[128]; 481 char pending_buf[128]; 482 const struct TALER_MERCHANT_ContractBaseTerms *ct = NULL; 483 484 /* Bail early if we already have an error */ 485 if (TALER_EC_NONE != po->result) 486 return; 487 488 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 489 "Adding order `%s' (%llu) to result set at instance `%s'\n", 490 orig_order_id, 491 (unsigned long long) order_serial, 492 po->instance_id); 493 qs = TALER_MERCHANTDB_lookup_order_status_by_serial (TMH_db, 494 po->instance_id, 495 order_serial, 496 &order_id, 497 &h_contract_terms, 498 &paid); 499 if (qs < 0) 500 { 501 GNUNET_break (0); 502 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 503 return; 504 } 505 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 506 { 507 /* Contract terms don't exist, so the order cannot be paid. */ 508 paid = false; 509 if (NULL == orig_order_id) 510 { 511 /* Got a DB trigger about a new proposal, but it 512 was already deleted again. Just ignore the event. */ 513 return; 514 } 515 order_id = GNUNET_strdup (orig_order_id); 516 } 517 518 { 519 /* First try to find the order in the contracts */ 520 uint64_t os; 521 bool session_matches; 522 523 qs = TALER_MERCHANTDB_lookup_contract_terms3 (TMH_db, 524 po->instance_id, 525 order_id, 526 NULL, 527 &terms, 528 &os, 529 &paid, 530 &wired, 531 &session_matches, 532 NULL, 533 &choice_index); 534 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 535 { 536 GNUNET_break (os == order_serial); 537 contract = TALER_MERCHANT_contract_parse (terms); 538 if (NULL == contract) 539 { 540 GNUNET_break (0); 541 po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID; 542 goto cleanup; 543 } 544 ct = contract->pc->base; 545 } 546 } 547 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 548 { 549 /* Might still be unclaimed, so try order table */ 550 struct TALER_MerchantPostDataHashP unused; 551 552 paid = false; 553 wired = false; 554 qs = TALER_MERCHANTDB_lookup_order (TMH_db, 555 po->instance_id, 556 order_id, 557 NULL, 558 &unused, 559 &terms); 560 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 561 { 562 order = TALER_MERCHANT_order_parse (terms); 563 if (NULL == order) 564 { 565 GNUNET_break (0); 566 po->result = TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID; 567 goto cleanup; 568 } 569 ct = order->base; 570 } 571 } 572 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 573 { 574 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 575 "Order %llu disappeared during iteration. Skipping.\n", 576 (unsigned long long) order_serial); 577 goto cleanup; 578 } 579 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) 580 { 581 GNUNET_break (0); 582 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 583 goto cleanup; 584 } 585 586 587 if (paid) 588 { 589 const struct TALER_Amount *brutto; 590 591 GNUNET_assert (NULL != contract); 592 switch (ct->version) 593 { 594 case TALER_MERCHANT_CONTRACT_VERSION_0: 595 brutto = &contract->pc->details.v0.brutto; 596 break; 597 case TALER_MERCHANT_CONTRACT_VERSION_1: 598 { 599 struct TALER_MERCHANT_ContractChoice *choice 600 = &contract->pc->details.v1.choices[choice_index]; 601 602 GNUNET_assert (choice_index < contract->pc->details.v1.choices_len); 603 brutto = &choice->amount; 604 } 605 break; 606 default: 607 GNUNET_break (0); 608 goto cleanup; 609 } 610 GNUNET_assert (GNUNET_OK == 611 TALER_amount_set_zero (brutto->currency, 612 &prc.total_refund_amount)); 613 GNUNET_assert (GNUNET_OK == 614 TALER_amount_set_zero (brutto->currency, 615 &prc.pending_refund_amount)); 616 617 qs = TALER_MERCHANTDB_lookup_refunds_detailed (TMH_db, 618 po->instance_id, 619 &h_contract_terms, 620 &process_refunds_cb, 621 &prc); 622 if (0 > qs) 623 { 624 GNUNET_break (0); 625 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 626 goto cleanup; 627 } 628 if (TALER_EC_NONE != prc.ec) 629 { 630 GNUNET_break (0); 631 po->result = prc.ec; 632 goto cleanup; 633 } 634 if (0 > TALER_amount_cmp (&prc.total_refund_amount, 635 brutto) && 636 GNUNET_TIME_absolute_is_future ( 637 contract->pc->refund_deadline.abs_time)) 638 refundable = true; 639 } 640 641 /* compute amount totals */ 642 amount = NULL; 643 switch (ct->version) 644 { 645 case TALER_MERCHANT_CONTRACT_VERSION_0: 646 { 647 amount = (NULL != contract) 648 ? &contract->pc->details.v0.brutto 649 : &order->details.v0.brutto; 650 651 if (TALER_amount_is_zero (amount) && 652 (po->of.wired != TALER_EXCHANGE_YNA_ALL) ) 653 { 654 /* If we are actually filtering by wire status, 655 and the order was over an amount of zero, 656 do not return it as wire status is not 657 exactly meaningful for orders over zero. */ 658 goto cleanup; 659 } 660 661 /* Accumulate order total */ 662 if (paid) 663 accumulate_total (po, 664 amount); 665 if (TALER_EC_NONE != po->result) 666 goto cleanup; 667 /* Accumulate refund totals (only meaningful for paid orders) */ 668 if (paid) 669 { 670 accumulate_refund_totals (po, 671 &prc.total_refund_amount, 672 &prc.pending_refund_amount); 673 if (TALER_EC_NONE != po->result) 674 goto cleanup; 675 } 676 } 677 break; 678 case TALER_MERCHANT_CONTRACT_VERSION_1: 679 if (-1 == choice_index) 680 choice_index = 0; /* default choice */ 681 if (NULL != contract) 682 { 683 struct TALER_MERCHANT_ContractChoice *choice 684 = &contract->pc->details.v1.choices[choice_index]; 685 686 GNUNET_assert (choice_index < contract->pc->details.v1.choices_len); 687 amount = &choice->amount; 688 /* Accumulate order total */ 689 accumulate_total (po, 690 amount); 691 if (TALER_EC_NONE != po->result) 692 goto cleanup; 693 /* Accumulate refund totals (only meaningful for paid orders) */ 694 if (paid) 695 { 696 accumulate_refund_totals (po, 697 &prc.total_refund_amount, 698 &prc.pending_refund_amount); 699 if (TALER_EC_NONE != po->result) 700 goto cleanup; 701 } 702 } 703 else 704 { 705 struct TALER_MERCHANT_OrderChoice *choice 706 = &order->details.v1.choices[choice_index]; 707 708 GNUNET_assert (choice_index < order->details.v1.choices_len); 709 amount = &choice->amount; 710 /* Accumulate order total */ 711 accumulate_total (po, 712 amount); 713 if (TALER_EC_NONE != po->result) 714 goto cleanup; 715 } 716 break; 717 default: 718 GNUNET_break (0); 719 po->result = TALER_EC_MERCHANT_GET_ORDERS_ID_INVALID_CONTRACT_VERSION; 720 goto cleanup; 721 } 722 723 /* convert amounts to strings (needed for some formats) */ 724 /* FIXME: use currency formatting rules in the future 725 instead of TALER_amount2s for human readability... */ 726 strcpy (amount_buf, 727 TALER_amount2s (amount)); 728 if (paid) 729 strcpy (refund_buf, 730 TALER_amount2s (&prc.total_refund_amount)); 731 if (paid) 732 strcpy (pending_buf, 733 TALER_amount2s (&prc.pending_refund_amount)); 734 735 switch (po->format) 736 { 737 case POF_JSON: 738 case POF_PDF: 739 GNUNET_assert ( 740 0 == 741 json_array_append_new ( 742 po->pa, 743 GNUNET_JSON_PACK ( 744 GNUNET_JSON_pack_string ("order_id", 745 order_id), 746 GNUNET_JSON_pack_uint64 ("row_id", 747 order_serial), 748 GNUNET_JSON_pack_timestamp ("timestamp", 749 creation_time), 750 TALER_JSON_pack_amount ("amount", 751 amount), 752 GNUNET_JSON_pack_allow_null ( 753 TALER_JSON_pack_amount ( 754 "refund_amount", 755 paid 756 ? &prc.total_refund_amount 757 : NULL)), 758 GNUNET_JSON_pack_allow_null ( 759 TALER_JSON_pack_amount ( 760 "pending_refund_amount", 761 paid 762 ? &prc.pending_refund_amount 763 : NULL)), 764 GNUNET_JSON_pack_string ("summary", 765 ct->summary), 766 GNUNET_JSON_pack_bool ("refundable", 767 refundable), 768 GNUNET_JSON_pack_bool ("paid", 769 paid)))); 770 break; 771 case POF_CSV: 772 { 773 size_t len = strlen (ct->summary); 774 size_t wpos = 0; 775 char *esummary; 776 struct tm *tm; 777 time_t t; 778 779 /* Escape 'summary' to double '"' as per RFC 4180, 2.7. */ 780 esummary = GNUNET_malloc (2 * len + 1); 781 for (size_t off = 0; off<len; off++) 782 { 783 if ('"' == ct->summary[off]) 784 esummary[wpos++] = '"'; 785 esummary[wpos++] = ct->summary[off]; 786 } 787 t = GNUNET_TIME_timestamp_to_s (creation_time); 788 tm = localtime (&t); 789 GNUNET_buffer_write_fstr ( 790 &po->csv, 791 "%s,%llu,%04u-%02u-%02u,%02u:%02u (%s),%llu,%s,%s,%s,\"%s\",%s,%s\r\n", 792 order_id, 793 (unsigned long long) order_serial, 794 tm->tm_year + 1900, 795 tm->tm_mon + 1, 796 tm->tm_mday, 797 tm->tm_hour, 798 tm->tm_min, 799 tm->tm_zone, 800 (unsigned long long) t, 801 amount_buf, 802 paid ? refund_buf : "", 803 paid ? pending_buf : "", 804 esummary, 805 refundable ? "yes" : "no", 806 paid ? "yes" : "no"); 807 GNUNET_free (esummary); 808 break; 809 } 810 case POF_XML: 811 { 812 char *esummary = TALER_escape_xml (ct->summary); 813 char creation_time_s[128]; 814 const struct tm *tm; 815 time_t tt; 816 817 tt = (time_t) GNUNET_TIME_timestamp_to_s (creation_time); 818 tm = gmtime (&tt); 819 strftime (creation_time_s, 820 sizeof (creation_time_s), 821 "%Y-%m-%dT%H:%M:%S", 822 tm); 823 GNUNET_buffer_write_fstr ( 824 &po->xml, 825 "<Row>" 826 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 827 "<Cell ss:StyleID=\"DateFormat\"><Data ss:Type=\"DateTime\">%s</Data></Cell>" 828 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 829 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 830 "<Cell><Data ss:Type=\"String\">%s</Data></Cell>" 831 "<Cell ss:Formula=\"=%s()\"><Data ss:Type=\"Boolean\">%s</Data></Cell>" 832 "</Row>\n", 833 order_id, 834 creation_time_s, 835 amount_buf, 836 paid ? refund_buf : "", 837 NULL != esummary ? esummary : "", 838 paid ? "TRUE" : "FALSE", 839 paid ? "1" : "0"); 840 GNUNET_free (esummary); 841 } 842 break; 843 } /* end switch po->format */ 844 845 cleanup: 846 json_decref (terms); 847 GNUNET_free (order_id); 848 if (NULL != order) 849 { 850 TALER_MERCHANT_order_free (order); 851 order = NULL; 852 } 853 if (NULL != contract) 854 { 855 TALER_MERCHANT_contract_free (contract); 856 contract = NULL; 857 } 858 } 859 860 861 /** 862 * We have received a trigger from the database 863 * that we should (possibly) resume some requests. 864 * 865 * @param cls a `struct TMH_MerchantInstance` 866 * @param extra a `struct TMH_OrderChangeEventP` 867 * @param extra_size number of bytes in @a extra 868 */ 869 static void 870 resume_by_event (void *cls, 871 const void *extra, 872 size_t extra_size) 873 { 874 struct TMH_MerchantInstance *mi = cls; 875 const struct TMH_OrderChangeEventDetailsP *oce = extra; 876 struct TMH_PendingOrder *pn; 877 enum TMH_OrderStateFlags osf; 878 uint64_t order_serial_id; 879 struct GNUNET_TIME_Timestamp date; 880 881 if (sizeof (*oce) != extra_size) 882 { 883 GNUNET_break (0); 884 return; 885 } 886 osf = (enum TMH_OrderStateFlags) (int) ntohl (oce->order_state); 887 order_serial_id = GNUNET_ntohll (oce->order_serial_id); 888 date = GNUNET_TIME_timestamp_ntoh (oce->execution_date); 889 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 890 "Received notification about order %llu\n", 891 (unsigned long long) order_serial_id); 892 for (struct TMH_PendingOrder *po = mi->po_head; 893 NULL != po; 894 po = pn) 895 { 896 pn = po->next; 897 if (! ( ( ((TALER_EXCHANGE_YNA_YES == po->of.paid) == 898 (0 != (osf & TMH_OSF_PAID))) || 899 (TALER_EXCHANGE_YNA_ALL == po->of.paid) ) && 900 ( ((TALER_EXCHANGE_YNA_YES == po->of.refunded) == 901 (0 != (osf & TMH_OSF_REFUNDED))) || 902 (TALER_EXCHANGE_YNA_ALL == po->of.refunded) ) && 903 ( ((TALER_EXCHANGE_YNA_YES == po->of.wired) == 904 (0 != (osf & TMH_OSF_WIRED))) || 905 (TALER_EXCHANGE_YNA_ALL == po->of.wired) ) ) ) 906 { 907 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 908 "Client %p waits on different order type\n", 909 po); 910 continue; 911 } 912 if (po->of.delta > 0) 913 { 914 if (order_serial_id < po->of.start_row) 915 { 916 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 917 "Client %p waits on different order row\n", 918 po); 919 continue; 920 } 921 if (GNUNET_TIME_timestamp_cmp (date, 922 <, 923 po->of.date)) 924 { 925 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 926 "Client %p waits on different order date\n", 927 po); 928 continue; 929 } 930 po->of.delta--; 931 } 932 else 933 { 934 if (order_serial_id > po->of.start_row) 935 { 936 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 937 "Client %p waits on different order row\n", 938 po); 939 continue; 940 } 941 if (GNUNET_TIME_timestamp_cmp (date, 942 >, 943 po->of.date)) 944 { 945 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 946 "Client %p waits on different order date\n", 947 po); 948 continue; 949 } 950 po->of.delta++; 951 } 952 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 953 "Waking up client %p!\n", 954 po); 955 add_order (po, 956 NULL, 957 order_serial_id, 958 date); 959 GNUNET_assert (po->in_dll); 960 GNUNET_CONTAINER_DLL_remove (mi->po_head, 961 mi->po_tail, 962 po); 963 po->in_dll = false; 964 MHD_resume_connection (po->con); 965 TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ 966 } 967 if (NULL == mi->po_head) 968 { 969 TALER_MERCHANTDB_event_listen_cancel (mi->po_eh); 970 mi->po_eh = NULL; 971 } 972 } 973 974 975 /** 976 * There has been a change or addition of a new @a order_id. Wake up 977 * long-polling clients that may have been waiting for this event. 978 * 979 * @param mi the instance where the order changed 980 * @param osf order state flags 981 * @param date execution date of the order 982 * @param order_serial_id serial ID of the order in the database 983 */ 984 void 985 TMH_notify_order_change (struct TMH_MerchantInstance *mi, 986 enum TMH_OrderStateFlags osf, 987 struct GNUNET_TIME_Timestamp date, 988 uint64_t order_serial_id) 989 { 990 struct TMH_OrderChangeEventDetailsP oce = { 991 .order_serial_id = GNUNET_htonll (order_serial_id), 992 .execution_date = GNUNET_TIME_timestamp_hton (date), 993 .order_state = htonl (osf) 994 }; 995 struct TMH_OrderChangeEventP eh = { 996 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE), 997 .header.size = htons (sizeof (eh)), 998 .merchant_pub = mi->merchant_pub 999 }; 1000 1001 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1002 "Notifying clients of new order %llu at %s\n", 1003 (unsigned long long) order_serial_id, 1004 TALER_B2S (&mi->merchant_pub)); 1005 TALER_MERCHANTDB_event_notify (TMH_db, 1006 &eh.header, 1007 &oce, 1008 sizeof (oce)); 1009 } 1010 1011 1012 /** 1013 * Transforms an (untrusted) input filter into a Postgresql LIKE filter. 1014 * Escapes "%" and "_" in the @a input and adds "%" at the beginning 1015 * and the end to turn the @a input into a suitable Postgresql argument. 1016 * 1017 * @param input text to turn into a substring match expression, or NULL 1018 * @return NULL if @a input was NULL, otherwise transformed @a input 1019 */ 1020 static char * 1021 tr (const char *input) 1022 { 1023 char *out; 1024 size_t slen; 1025 size_t wpos; 1026 1027 if (NULL == input) 1028 return NULL; 1029 slen = strlen (input); 1030 out = GNUNET_malloc (slen * 2 + 3); 1031 wpos = 0; 1032 out[wpos++] = '%'; 1033 for (size_t i = 0; i<slen; i++) 1034 { 1035 char c = input[i]; 1036 1037 if ( (c == '%') || 1038 (c == '_') ) 1039 out[wpos++] = '\\'; 1040 out[wpos++] = c; 1041 } 1042 out[wpos++] = '%'; 1043 GNUNET_assert (wpos < slen * 2 + 3); 1044 return out; 1045 } 1046 1047 1048 /** 1049 * Function called with the result of a #TALER_MHD_typst() operation. 1050 * 1051 * @param cls closure, a `struct TMH_PendingOrder *` 1052 * @param tr result of the operation 1053 */ 1054 static void 1055 pdf_cb (void *cls, 1056 const struct TALER_MHD_TypstResponse *tr) 1057 { 1058 struct TMH_PendingOrder *po = cls; 1059 1060 po->tc = NULL; 1061 GNUNET_CONTAINER_DLL_remove (pdf_head, 1062 pdf_tail, 1063 po); 1064 if (TALER_EC_NONE != tr->ec) 1065 { 1066 po->http_status 1067 = TALER_ErrorCode_get_http_status (tr->ec); 1068 po->response 1069 = TALER_MHD_make_error (tr->ec, 1070 tr->details.hint); 1071 } 1072 else 1073 { 1074 po->http_status = MHD_HTTP_OK; 1075 po->response = TALER_MHD_response_from_pdf_file (tr->details.filename); 1076 } 1077 MHD_resume_connection (po->con); 1078 TALER_MHD_daemon_trigger (); 1079 } 1080 1081 1082 /** 1083 * Build the final response for a completed (non-long-poll) request and 1084 * queue it on @a connection. 1085 * 1086 * Handles all formats (JSON, CSV, XML, PDF). For PDF this may suspend 1087 * the connection while Typst runs asynchronously; in that case the caller 1088 * must return #MHD_YES immediately. 1089 * 1090 * @param po the pending order state (already fully populated) 1091 * @param connection the MHD connection 1092 * @param mi the merchant instance 1093 * @return MHD result code 1094 */ 1095 static enum MHD_Result 1096 reply_orders (struct TMH_PendingOrder *po, 1097 struct MHD_Connection *connection, 1098 struct TMH_MerchantInstance *mi) 1099 { 1100 char total_buf[128]; 1101 char refund_buf[128]; 1102 char pending_buf[128]; 1103 1104 switch (po->format) 1105 { 1106 case POF_JSON: 1107 return TALER_MHD_REPLY_JSON_PACK ( 1108 connection, 1109 MHD_HTTP_OK, 1110 GNUNET_JSON_pack_array_incref ("orders", 1111 po->pa)); 1112 case POF_CSV: 1113 { 1114 struct MHD_Response *resp; 1115 enum 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 GNUNET_buffer_write_fstr ( 1134 &po->csv, 1135 "Total (paid %s only),,,,%s,%s,%s,,,\r\n", 1136 tai->currency, 1137 total_buf, 1138 refund_buf, 1139 pending_buf); 1140 } 1141 GNUNET_buffer_write_str (&po->csv, 1142 CSV_FOOTER); 1143 resp = MHD_create_response_from_buffer (po->csv.position, 1144 po->csv.mem, 1145 MHD_RESPMEM_MUST_COPY); 1146 TALER_MHD_add_global_headers (resp, 1147 false); 1148 GNUNET_break (MHD_YES == 1149 MHD_add_response_header (resp, 1150 MHD_HTTP_HEADER_CONTENT_TYPE, 1151 "text/csv")); 1152 mret = MHD_queue_response (connection, 1153 MHD_HTTP_OK, 1154 resp); 1155 MHD_destroy_response (resp); 1156 return mret; 1157 } 1158 case POF_XML: 1159 { 1160 struct MHD_Response *resp; 1161 enum MHD_Result mret; 1162 1163 for (unsigned int i = 0; i<po->total_amount.taa_size; i++) 1164 { 1165 struct TALER_Amount *tai = &po->total_amount.taa[i]; 1166 const struct TALER_Amount *r; 1167 1168 strcpy (total_buf, 1169 TALER_amount2s (tai)); 1170 r = TALER_amount_set_find (tai->currency, 1171 &po->total_refund_amount); 1172 strcpy (refund_buf, 1173 TALER_amount2s (r)); 1174 r = TALER_amount_set_find (tai->currency, 1175 &po->total_pending_refund_amount); 1176 strcpy (pending_buf, 1177 TALER_amount2s (r)); 1178 1179 /* Append totals row with paid and refunded amount columns */ 1180 GNUNET_buffer_write_fstr ( 1181 &po->xml, 1182 "<Row>" 1183 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">Total (paid %s only)</Data></Cell>" 1184 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>" 1185 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>" 1186 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\">%s</Data></Cell>" 1187 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>" 1188 "<Cell ss:StyleID=\"Total\"><Data ss:Type=\"String\"></Data></Cell>" 1189 "</Row>\n", 1190 tai->currency, 1191 total_buf, 1192 refund_buf); 1193 } 1194 GNUNET_buffer_write_str (&po->xml, 1195 XML_FOOTER); 1196 resp = MHD_create_response_from_buffer (po->xml.position, 1197 po->xml.mem, 1198 MHD_RESPMEM_MUST_COPY); 1199 TALER_MHD_add_global_headers (resp, 1200 false); 1201 GNUNET_break (MHD_YES == 1202 MHD_add_response_header (resp, 1203 MHD_HTTP_HEADER_CONTENT_TYPE, 1204 "application/vnd.ms-excel")); 1205 mret = MHD_queue_response (connection, 1206 MHD_HTTP_OK, 1207 resp); 1208 MHD_destroy_response (resp); 1209 return mret; 1210 } 1211 case POF_PDF: 1212 { 1213 /* Build the JSON document for Typst, passing all totals */ 1214 json_t *root; 1215 struct TALER_MHD_TypstDocument doc; 1216 json_t *ta = json_array (); 1217 json_t *ra = json_array (); 1218 json_t *pa = json_array (); 1219 1220 GNUNET_assert (NULL != ta); 1221 GNUNET_assert (NULL != ra); 1222 GNUNET_assert (NULL != pa); 1223 for (unsigned int i = 0; i<po->total_amount.taa_size; i++) 1224 { 1225 struct TALER_Amount *tai = &po->total_amount.taa[i]; 1226 const struct TALER_Amount *r; 1227 1228 GNUNET_assert (0 == 1229 json_array_append_new (ta, 1230 TALER_JSON_from_amount (tai))); 1231 r = TALER_amount_set_find (tai->currency, 1232 &po->total_refund_amount); 1233 GNUNET_assert (0 == 1234 json_array_append_new (ra, 1235 TALER_JSON_from_amount (r))); 1236 r = TALER_amount_set_find (tai->currency, 1237 &po->total_pending_refund_amount); 1238 GNUNET_assert (0 == 1239 json_array_append_new (pa, 1240 TALER_JSON_from_amount (r))); 1241 } 1242 root = GNUNET_JSON_PACK ( 1243 GNUNET_JSON_pack_string ("business_name", 1244 mi->settings.name), 1245 GNUNET_JSON_pack_array_incref ("orders", 1246 po->pa), 1247 GNUNET_JSON_pack_array_steal ("total_amounts", 1248 ta), 1249 GNUNET_JSON_pack_array_steal ("total_refund_amounts", 1250 ra), 1251 GNUNET_JSON_pack_array_steal ("total_pending_refund_amounts", 1252 pa)); 1253 doc.form_name = "orders"; 1254 doc.form_version = "0.0.0"; 1255 doc.data = root; 1256 1257 po->tc = TALER_MHD_typst (TALER_MERCHANT_project_data (), 1258 TMH_cfg, 1259 false, /* remove on exit */ 1260 "merchant", 1261 1, /* one document */ 1262 &doc, 1263 &pdf_cb, 1264 po); 1265 json_decref (root); 1266 if (NULL == po->tc) 1267 { 1268 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1269 "Client requested PDF, but Typst is unavailable\n"); 1270 return TALER_MHD_reply_with_error ( 1271 connection, 1272 MHD_HTTP_NOT_IMPLEMENTED, 1273 TALER_EC_EXCHANGE_GENERIC_NO_TYPST_OR_PDFTK, 1274 NULL); 1275 } 1276 GNUNET_CONTAINER_DLL_insert (pdf_head, 1277 pdf_tail, 1278 po); 1279 MHD_suspend_connection (connection); 1280 return MHD_YES; 1281 } 1282 } /* end switch */ 1283 GNUNET_assert (0); 1284 return MHD_NO; 1285 } 1286 1287 1288 /** 1289 * Handle a GET "/orders" request. 1290 * 1291 * @param rh context of the handler 1292 * @param connection the MHD connection to handle 1293 * @param[in,out] hc context with further information about the request 1294 * @return MHD result code 1295 */ 1296 enum MHD_Result 1297 TMH_private_get_orders (const struct TMH_RequestHandler *rh, 1298 struct MHD_Connection *connection, 1299 struct TMH_HandlerContext *hc) 1300 { 1301 struct TMH_PendingOrder *po = hc->ctx; 1302 struct TMH_MerchantInstance *mi = hc->instance; 1303 enum GNUNET_DB_QueryStatus qs; 1304 1305 if (NULL != po) 1306 { 1307 if (TALER_EC_NONE != po->result) 1308 { 1309 /* Resumed from long-polling with error */ 1310 GNUNET_break (0); 1311 return TALER_MHD_reply_with_error (connection, 1312 MHD_HTTP_INTERNAL_SERVER_ERROR, 1313 po->result, 1314 NULL); 1315 } 1316 if (POF_PDF == po->format) 1317 { 1318 /* resumed from long-polling or from Typst PDF generation */ 1319 /* We really must have a response in this case */ 1320 if (NULL == po->response) 1321 { 1322 GNUNET_break (0); 1323 return MHD_NO; 1324 } 1325 return MHD_queue_response (connection, 1326 po->http_status, 1327 po->response); 1328 } 1329 return reply_orders (po, 1330 connection, 1331 mi); 1332 } 1333 po = GNUNET_new (struct TMH_PendingOrder); 1334 hc->ctx = po; 1335 hc->cc = &cleanup; 1336 po->con = connection; 1337 po->pa = json_array (); 1338 GNUNET_assert (NULL != po->pa); 1339 po->instance_id = mi->settings.id; 1340 po->mi = mi; 1341 1342 /* Determine desired output format from Accept header */ 1343 { 1344 const char *mime; 1345 1346 mime = MHD_lookup_connection_value (connection, 1347 MHD_HEADER_KIND, 1348 MHD_HTTP_HEADER_ACCEPT); 1349 if (NULL == mime) 1350 mime = "application/json"; 1351 if (0 == strcmp (mime, 1352 "*/*")) 1353 mime = "application/json"; 1354 if (0 == strcmp (mime, 1355 "application/json")) 1356 { 1357 po->format = POF_JSON; 1358 } 1359 else if (0 == strcmp (mime, 1360 "text/csv")) 1361 { 1362 po->format = POF_CSV; 1363 GNUNET_buffer_write_str (&po->csv, 1364 CSV_HEADER); 1365 } 1366 else if (0 == strcmp (mime, 1367 "application/vnd.ms-excel")) 1368 { 1369 po->format = POF_XML; 1370 GNUNET_buffer_write_str (&po->xml, 1371 XML_HEADER); 1372 } 1373 else if (0 == strcmp (mime, 1374 "application/pdf")) 1375 { 1376 po->format = POF_PDF; 1377 } 1378 else 1379 { 1380 GNUNET_break_op (0); 1381 return TALER_MHD_REPLY_JSON_PACK ( 1382 connection, 1383 MHD_HTTP_NOT_ACCEPTABLE, 1384 GNUNET_JSON_pack_string ("hint", 1385 mime)); 1386 } 1387 } 1388 1389 if (! (TALER_MHD_arg_to_yna (connection, 1390 "paid", 1391 TALER_EXCHANGE_YNA_ALL, 1392 &po->of.paid)) ) 1393 { 1394 GNUNET_break_op (0); 1395 return TALER_MHD_reply_with_error (connection, 1396 MHD_HTTP_BAD_REQUEST, 1397 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1398 "paid"); 1399 } 1400 if (! (TALER_MHD_arg_to_yna (connection, 1401 "refunded", 1402 TALER_EXCHANGE_YNA_ALL, 1403 &po->of.refunded)) ) 1404 { 1405 GNUNET_break_op (0); 1406 return TALER_MHD_reply_with_error (connection, 1407 MHD_HTTP_BAD_REQUEST, 1408 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1409 "refunded"); 1410 } 1411 if (! (TALER_MHD_arg_to_yna (connection, 1412 "wired", 1413 TALER_EXCHANGE_YNA_ALL, 1414 &po->of.wired)) ) 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 "wired"); 1421 } 1422 po->of.delta = -20; 1423 /* deprecated in protocol v12 */ 1424 TALER_MHD_parse_request_snumber (connection, 1425 "delta", 1426 &po->of.delta); 1427 /* since protocol v12 */ 1428 TALER_MHD_parse_request_snumber (connection, 1429 "limit", 1430 &po->of.delta); 1431 if ( (-MAX_DELTA > po->of.delta) || 1432 (po->of.delta > MAX_DELTA) ) 1433 { 1434 GNUNET_break_op (0); 1435 return TALER_MHD_reply_with_error (connection, 1436 MHD_HTTP_BAD_REQUEST, 1437 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1438 "limit"); 1439 } 1440 { 1441 const char *date_s_str; 1442 1443 date_s_str = MHD_lookup_connection_value (connection, 1444 MHD_GET_ARGUMENT_KIND, 1445 "date_s"); 1446 if (NULL == date_s_str) 1447 { 1448 if (po->of.delta > 0) 1449 po->of.date = GNUNET_TIME_UNIT_ZERO_TS; 1450 else 1451 po->of.date = GNUNET_TIME_UNIT_FOREVER_TS; 1452 } 1453 else 1454 { 1455 char dummy; 1456 unsigned long long ll; 1457 1458 if (1 != 1459 sscanf (date_s_str, 1460 "%llu%c", 1461 &ll, 1462 &dummy)) 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 "date_s"); 1469 } 1470 1471 po->of.date = GNUNET_TIME_absolute_to_timestamp ( 1472 GNUNET_TIME_absolute_from_s (ll)); 1473 if (GNUNET_TIME_absolute_is_never (po->of.date.abs_time)) 1474 { 1475 GNUNET_break_op (0); 1476 return TALER_MHD_reply_with_error (connection, 1477 MHD_HTTP_BAD_REQUEST, 1478 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1479 "date_s"); 1480 } 1481 } 1482 } 1483 if (po->of.delta > 0) 1484 { 1485 struct GNUNET_TIME_Relative duration 1486 = GNUNET_TIME_UNIT_FOREVER_REL; 1487 struct GNUNET_TIME_Absolute cut_off; 1488 1489 TALER_MHD_parse_request_rel_time (connection, 1490 "max_age", 1491 &duration); 1492 cut_off = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (), 1493 duration); 1494 po->of.date = GNUNET_TIME_timestamp_max ( 1495 po->of.date, 1496 GNUNET_TIME_absolute_to_timestamp (cut_off)); 1497 } 1498 if (po->of.delta > 0) 1499 po->of.start_row = 0; 1500 else 1501 po->of.start_row = INT64_MAX; 1502 /* deprecated in protocol v12 */ 1503 TALER_MHD_parse_request_number (connection, 1504 "start", 1505 &po->of.start_row); 1506 /* since protocol v12 */ 1507 TALER_MHD_parse_request_number (connection, 1508 "offset", 1509 &po->of.start_row); 1510 if (INT64_MAX < po->of.start_row) 1511 { 1512 GNUNET_break_op (0); 1513 return TALER_MHD_reply_with_error (connection, 1514 MHD_HTTP_BAD_REQUEST, 1515 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1516 "offset"); 1517 } 1518 po->summary_filter = tr (MHD_lookup_connection_value (connection, 1519 MHD_GET_ARGUMENT_KIND, 1520 "summary_filter")); 1521 po->of.summary_filter = po->summary_filter; /* just an (read-only) alias! */ 1522 po->of.session_id 1523 = MHD_lookup_connection_value (connection, 1524 MHD_GET_ARGUMENT_KIND, 1525 "session_id"); 1526 po->of.fulfillment_url 1527 = MHD_lookup_connection_value (connection, 1528 MHD_GET_ARGUMENT_KIND, 1529 "fulfillment_url"); 1530 TALER_MHD_parse_request_timeout (connection, 1531 &po->long_poll_timeout); 1532 if (GNUNET_TIME_absolute_is_never (po->long_poll_timeout)) 1533 { 1534 GNUNET_break_op (0); 1535 return TALER_MHD_reply_with_error (connection, 1536 MHD_HTTP_BAD_REQUEST, 1537 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1538 "timeout_ms"); 1539 } 1540 if ( (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) && 1541 (NULL == mi->po_eh) ) 1542 { 1543 struct TMH_OrderChangeEventP change_eh = { 1544 .header.type = htons (TALER_DBEVENT_MERCHANT_ORDERS_CHANGE), 1545 .header.size = htons (sizeof (change_eh)), 1546 .merchant_pub = mi->merchant_pub 1547 }; 1548 1549 mi->po_eh = TALER_MERCHANTDB_event_listen (TMH_db, 1550 &change_eh.header, 1551 GNUNET_TIME_UNIT_FOREVER_REL, 1552 &resume_by_event, 1553 mi); 1554 } 1555 1556 po->of.timeout = GNUNET_TIME_absolute_get_remaining (po->long_poll_timeout); 1557 1558 qs = TALER_MERCHANTDB_lookup_orders (TMH_db, 1559 po->instance_id, 1560 &po->of, 1561 &add_order, 1562 po); 1563 if (0 > qs) 1564 { 1565 GNUNET_break (0); 1566 po->result = TALER_EC_GENERIC_DB_FETCH_FAILED; 1567 } 1568 if (TALER_EC_NONE != po->result) 1569 { 1570 GNUNET_break (0); 1571 return TALER_MHD_reply_with_error (connection, 1572 MHD_HTTP_INTERNAL_SERVER_ERROR, 1573 po->result, 1574 NULL); 1575 } 1576 if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) && 1577 (GNUNET_TIME_absolute_is_future (po->long_poll_timeout)) ) 1578 { 1579 GNUNET_assert (NULL == po->order_timeout_task); 1580 po->order_timeout_task 1581 = GNUNET_SCHEDULER_add_at (po->long_poll_timeout, 1582 &order_timeout, 1583 po); 1584 GNUNET_CONTAINER_DLL_insert (mi->po_head, 1585 mi->po_tail, 1586 po); 1587 po->in_dll = true; 1588 MHD_suspend_connection (connection); 1589 return MHD_YES; 1590 } 1591 return reply_orders (po, 1592 connection, 1593 mi); 1594 } 1595 1596 1597 /* end of taler-merchant-httpd_get-private-orders.c */