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