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