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