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