taler-merchant-httpd_get-templates-TEMPLATE_ID.c (29945B)
1 /* 2 This file is part of TALER 3 (C) 2022-2025 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-templates-TEMPLATE_ID.c 18 * @brief implement GET /templates/$ID 19 * @author Priscilla HUANG 20 */ 21 #include "platform.h" 22 #include "taler-merchant-httpd_get-templates-TEMPLATE_ID.h" 23 #include "taler-merchant-httpd_exchanges.h" 24 #include "taler-merchant-httpd_helper.h" 25 #include <taler/taler_json_lib.h> 26 #include "merchant-database/iterate_categories_by_ids.h" 27 #include "merchant-database/iterate_custom_units_by_names.h" 28 #include "merchant-database/iterate_inventory_products.h" 29 #include "merchant-database/iterate_inventory_products_filtered.h" 30 #include "merchant-database/get_template.h" 31 32 33 /** 34 * How long do we wait at most for exchange /keys data? 35 */ 36 #define MAX_KEYS_WAIT \ 37 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500) 38 39 40 struct GetTemplateContext; 41 42 43 /** 44 * Pending lookup of one exchange's keys. 45 */ 46 struct ExchangeLookup 47 { 48 /** 49 * Kept in a DLL. 50 */ 51 struct ExchangeLookup *next; 52 53 /** 54 * Kept in a DLL. 55 */ 56 struct ExchangeLookup *prev; 57 58 /** 59 * Parent request. 60 */ 61 struct GetTemplateContext *gtc; 62 63 /** 64 * Exchange base URL. 65 */ 66 char *url; 67 68 /** 69 * Pending keys operation. 70 */ 71 struct TMH_EXCHANGES_KeysOperation *ko; 72 }; 73 74 75 /** 76 * Context for a wallet-facing template lookup. 77 */ 78 struct GetTemplateContext 79 { 80 /** 81 * Kept in a DLL while suspended. 82 */ 83 struct GetTemplateContext *next; 84 85 /** 86 * Kept in a DLL while suspended. 87 */ 88 struct GetTemplateContext *prev; 89 90 /** 91 * Request handler context. 92 */ 93 struct TMH_HandlerContext *hc; 94 95 /** 96 * Connection handling this request. 97 */ 98 struct MHD_Connection *connection; 99 100 /** 101 * Pending exchange lookups. 102 */ 103 struct ExchangeLookup *lookup_head; 104 105 /** 106 * Pending exchange lookups. 107 */ 108 struct ExchangeLookup *lookup_tail; 109 110 /** 111 * Task that limits exchange lookup latency. 112 */ 113 struct GNUNET_SCHEDULER_Task *timeout_task; 114 115 /** 116 * Wallet-facing template contract. 117 */ 118 json_t *template_contract; 119 120 /** 121 * Editable defaults, NULL if absent. 122 */ 123 json_t *editable_defaults; 124 125 /** 126 * Merchant information snapshot. 127 */ 128 json_t *merchant; 129 130 /** 131 * Compatible exchange candidates found so far. 132 */ 133 json_t *exchange_candidates; 134 135 /** 136 * Merchant public key snapshot. 137 */ 138 struct TALER_MerchantPublicKeyP merchant_pub; 139 140 /** 141 * #GNUNET_YES while suspended, #GNUNET_NO otherwise, and 142 * #GNUNET_SYSERR when resumed for shutdown. 143 */ 144 enum GNUNET_GenericReturnValue suspended; 145 }; 146 147 148 /** 149 * Head of suspended template requests. 150 */ 151 static struct GetTemplateContext *gtc_head; 152 153 /** 154 * Tail of suspended template requests. 155 */ 156 static struct GetTemplateContext *gtc_tail; 157 158 159 /** 160 * Determine the currency the client must pay in, if the template 161 * requires a particular one without fixing the amount. 162 * 163 * @param template_contract contract of the template 164 * @return currency to require, NULL if the template does not say 165 */ 166 static const char * 167 get_required_currency (const json_t *template_contract) 168 { 169 const json_t *currency; 170 171 currency = json_object_get (template_contract, 172 "currency"); 173 if (NULL == currency) 174 return NULL; 175 if (! json_is_string (currency)) 176 { 177 GNUNET_break (0); 178 return NULL; 179 } 180 return json_string_value (currency); 181 } 182 183 184 /** 185 * Test whether @a method is already in @a methods. 186 * 187 * @param methods JSON array of strings 188 * @param method method to find 189 * @return true if present 190 */ 191 static bool 192 wire_method_present (const json_t *methods, 193 const char *method) 194 { 195 const json_t *entry; 196 size_t index; 197 198 json_array_foreach (methods, index, entry) 199 { 200 if (0 == strcasecmp (json_string_value (entry), 201 method)) 202 return true; 203 } 204 return false; 205 } 206 207 208 /** 209 * Test whether an exchange has an account using @a wire_method. 210 * 211 * @param keys exchange keys 212 * @param wire_method wire method to find 213 * @return true if the method is supported 214 */ 215 static bool 216 exchange_has_wire_method (const struct TALER_EXCHANGE_Keys *keys, 217 const char *wire_method) 218 { 219 for (unsigned int i = 0; i<keys->accounts_len; i++) 220 { 221 struct TALER_FullPayto full_payto = keys->accounts[i].fpayto_uri; 222 char *method; 223 bool matches; 224 225 method = TALER_payto_get_method (full_payto.full_payto); 226 matches = (0 == strcasecmp (method, 227 wire_method)); 228 GNUNET_free (method); 229 if (matches) 230 return true; 231 } 232 return false; 233 } 234 235 236 /** 237 * Add an exchange candidate based on current key data. 238 * 239 * @param gtc request context 240 * @param url exchange base URL 241 * @param keys exchange keys 242 * @param exchange exchange handle 243 */ 244 static void 245 add_exchange_candidate (struct GetTemplateContext *gtc, 246 const char *url, 247 const struct TALER_EXCHANGE_Keys *keys, 248 const struct TMH_Exchange *exchange) 249 { 250 const char *currency; 251 json_t *wire_methods; 252 253 if (NULL == keys) 254 return; 255 currency = TMH_EXCHANGES_get_currency (exchange); 256 if ( (NULL == currency) || 257 (0 != strcasecmp (currency, 258 keys->currency)) ) 259 return; 260 wire_methods = json_array (); 261 GNUNET_assert (NULL != wire_methods); 262 for (const struct TMH_WireMethod *wm = gtc->hc->instance->wm_head; 263 NULL != wm; 264 wm = wm->next) 265 { 266 struct TALER_NormalizedPayto np; 267 bool allowed; 268 269 if (! wm->active) 270 continue; 271 if (! exchange_has_wire_method (keys, 272 wm->wire_method)) 273 continue; 274 np = TALER_payto_normalize (wm->payto_uri); 275 allowed = TALER_EXCHANGE_keys_test_account_allowed (keys, 276 false, 277 np); 278 GNUNET_free (np.normalized_payto); 279 if ( (! allowed) || 280 wire_method_present (wire_methods, 281 wm->wire_method) ) 282 continue; 283 GNUNET_assert (0 == 284 json_array_append_new (wire_methods, 285 json_string (wm->wire_method))); 286 } 287 if (0 == json_array_size (wire_methods)) 288 { 289 json_decref (wire_methods); 290 return; 291 } 292 GNUNET_assert (0 == 293 json_array_append_new ( 294 gtc->exchange_candidates, 295 GNUNET_JSON_PACK ( 296 GNUNET_JSON_pack_string ("base_url", 297 url), 298 GNUNET_JSON_pack_string ("currency", 299 currency), 300 GNUNET_JSON_pack_data_auto ( 301 "master_pub", 302 TMH_EXCHANGES_get_master_pub (exchange)), 303 GNUNET_JSON_pack_array_steal ("wire_methods", 304 wire_methods)))); 305 } 306 307 308 /** 309 * Resume a template request after exchange discovery. 310 * 311 * @param gtc request to resume 312 */ 313 static void 314 resume_template_request (struct GetTemplateContext *gtc) 315 { 316 if (GNUNET_YES != gtc->suspended) 317 return; 318 if (NULL != gtc->timeout_task) 319 { 320 GNUNET_SCHEDULER_cancel (gtc->timeout_task); 321 gtc->timeout_task = NULL; 322 } 323 GNUNET_CONTAINER_DLL_remove (gtc_head, 324 gtc_tail, 325 gtc); 326 gtc->suspended = GNUNET_NO; 327 MHD_resume_connection (gtc->connection); 328 TALER_MHD_daemon_trigger (); 329 } 330 331 332 /** 333 * Exchange keys lookup completed. 334 * 335 * @param cls a `struct ExchangeLookup *` 336 * @param keys exchange keys, NULL on failure 337 * @param exchange exchange handle 338 */ 339 static void 340 exchange_keys_cb (void *cls, 341 struct TALER_EXCHANGE_Keys *keys, 342 struct TMH_Exchange *exchange) 343 { 344 struct ExchangeLookup *lookup = cls; 345 struct GetTemplateContext *gtc = lookup->gtc; 346 347 lookup->ko = NULL; 348 GNUNET_CONTAINER_DLL_remove (gtc->lookup_head, 349 gtc->lookup_tail, 350 lookup); 351 add_exchange_candidate (gtc, 352 lookup->url, 353 keys, 354 exchange); 355 GNUNET_free (lookup->url); 356 GNUNET_free (lookup); 357 if (NULL == gtc->lookup_head) 358 resume_template_request (gtc); 359 } 360 361 362 /** 363 * Start key discovery for one trusted exchange. 364 * 365 * @param cls a `struct GetTemplateContext *` 366 * @param url exchange base URL 367 * @param exchange exchange handle, unused 368 */ 369 static void 370 start_exchange_lookup (void *cls, 371 const char *url, 372 const struct TMH_Exchange *exchange) 373 { 374 struct GetTemplateContext *gtc = cls; 375 struct ExchangeLookup *lookup; 376 377 (void) exchange; 378 lookup = GNUNET_new (struct ExchangeLookup); 379 lookup->gtc = gtc; 380 lookup->url = GNUNET_strdup (url); 381 lookup->ko = TMH_EXCHANGES_keys4exchange (url, 382 false, 383 &exchange_keys_cb, 384 lookup); 385 if (NULL == lookup->ko) 386 { 387 GNUNET_free (lookup->url); 388 GNUNET_free (lookup); 389 return; 390 } 391 GNUNET_CONTAINER_DLL_insert (gtc->lookup_head, 392 gtc->lookup_tail, 393 lookup); 394 } 395 396 397 /** 398 * Stop waiting for exchange keys and return the partial candidate list. 399 * 400 * @param cls a `struct GetTemplateContext *` 401 */ 402 static void 403 exchange_lookup_timeout (void *cls) 404 { 405 struct GetTemplateContext *gtc = cls; 406 407 gtc->timeout_task = NULL; 408 while (NULL != gtc->lookup_head) 409 { 410 struct ExchangeLookup *lookup = gtc->lookup_head; 411 412 GNUNET_CONTAINER_DLL_remove (gtc->lookup_head, 413 gtc->lookup_tail, 414 lookup); 415 TMH_EXCHANGES_keys4exchange_cancel (lookup->ko); 416 GNUNET_free (lookup->url); 417 GNUNET_free (lookup); 418 } 419 resume_template_request (gtc); 420 } 421 422 423 /** 424 * Cleanup a template request context. 425 * 426 * @param cls a `struct GetTemplateContext *` 427 */ 428 static void 429 get_template_cleanup (void *cls) 430 { 431 struct GetTemplateContext *gtc = cls; 432 433 if (GNUNET_YES == gtc->suspended) 434 GNUNET_CONTAINER_DLL_remove (gtc_head, 435 gtc_tail, 436 gtc); 437 if (NULL != gtc->timeout_task) 438 GNUNET_SCHEDULER_cancel (gtc->timeout_task); 439 while (NULL != gtc->lookup_head) 440 { 441 struct ExchangeLookup *lookup = gtc->lookup_head; 442 443 GNUNET_CONTAINER_DLL_remove (gtc->lookup_head, 444 gtc->lookup_tail, 445 lookup); 446 TMH_EXCHANGES_keys4exchange_cancel (lookup->ko); 447 GNUNET_free (lookup->url); 448 GNUNET_free (lookup); 449 } 450 if (NULL != gtc->template_contract) 451 json_decref (gtc->template_contract); 452 if (NULL != gtc->editable_defaults) 453 json_decref (gtc->editable_defaults); 454 if (NULL != gtc->merchant) 455 json_decref (gtc->merchant); 456 if (NULL != gtc->exchange_candidates) 457 json_decref (gtc->exchange_candidates); 458 GNUNET_free (gtc); 459 } 460 461 462 void 463 TMH_force_get_templates_ID_resume (void) 464 { 465 while (NULL != gtc_head) 466 { 467 struct GetTemplateContext *gtc = gtc_head; 468 469 GNUNET_CONTAINER_DLL_remove (gtc_head, 470 gtc_tail, 471 gtc); 472 gtc->suspended = GNUNET_SYSERR; 473 MHD_resume_connection (gtc->connection); 474 TALER_MHD_daemon_trigger (); 475 } 476 } 477 478 479 /** 480 * Context for building inventory template payloads. 481 */ 482 struct InventoryPayloadContext 483 { 484 /** 485 * Selected category IDs (as JSON array). 486 */ 487 const json_t *selected_categories; 488 489 /** 490 * Selected product IDs (as JSON array) from contract_template. 491 */ 492 const json_t *selected_products; 493 494 /** 495 * Whether all products are selected. 496 */ 497 bool selected_all; 498 499 /** 500 * JSON array of products to build. 501 */ 502 json_t *products; 503 504 /** 505 * JSON array of categories to build. 506 */ 507 json_t *category_payload; 508 509 /** 510 * JSON array of units to build. 511 */ 512 json_t *unit_payload; 513 514 /** 515 * Set of categories referenced by the products. 516 */ 517 struct TMH_CategorySet category_set; 518 519 /** 520 * Set of unit identifiers referenced by the products. 521 */ 522 struct TMH_UnitSet unit_set; 523 }; 524 525 526 /** 527 * Release resources associated with an inventory payload context. 528 * 529 * @param ipc inventory payload context 530 */ 531 static void 532 inventory_payload_cleanup (struct InventoryPayloadContext *ipc) 533 { 534 if (NULL != ipc->products) 535 json_decref (ipc->products); 536 if (NULL != ipc->category_payload) 537 json_decref (ipc->category_payload); 538 if (NULL != ipc->unit_payload) 539 json_decref (ipc->unit_payload); 540 GNUNET_free (ipc->category_set.ids); 541 TMH_unit_set_clear (&ipc->unit_set); 542 ipc->products = NULL; 543 ipc->category_payload = NULL; 544 ipc->unit_payload = NULL; 545 ipc->category_set.len = 0; 546 } 547 548 549 /** 550 * Add inventory product to JSON payload. 551 * 552 * @param cls inventory payload context 553 * @param product_id product identifier 554 * @param pd product details 555 * @param num_categories number of categories 556 * @param categories category IDs 557 */ 558 static void 559 add_inventory_product ( 560 void *cls, 561 const char *product_id, 562 const struct TALER_MERCHANTDB_InventoryProductDetails *pd, 563 size_t num_categories, 564 const uint64_t *categories) 565 { 566 struct InventoryPayloadContext *ipc = cls; 567 json_t *jcategories; 568 json_t *product; 569 char remaining_stock_buf[64]; 570 571 jcategories = json_array (); 572 GNUNET_assert (NULL != jcategories); 573 for (size_t i = 0; i < num_categories; i++) 574 { 575 /* Adding per product category */ 576 TMH_category_set_add (&ipc->category_set, 577 categories[i]); 578 GNUNET_assert (0 == 579 json_array_append_new (jcategories, 580 json_integer (categories[i]))); 581 } 582 GNUNET_assert (0 < pd->price_array_length); 583 TALER_MERCHANT_vk_format_fractional_string ( 584 TALER_MERCHANT_VK_STOCK, 585 pd->remaining_stock, 586 pd->remaining_stock_frac, 587 sizeof (remaining_stock_buf), 588 remaining_stock_buf); 589 product = GNUNET_JSON_PACK ( 590 GNUNET_JSON_pack_string ("product_id", 591 product_id), 592 GNUNET_JSON_pack_string ("product_name", 593 pd->product_name), 594 GNUNET_JSON_pack_string ("description", 595 pd->description), 596 GNUNET_JSON_pack_allow_null ( 597 GNUNET_JSON_pack_object_incref ("description_i18n", 598 pd->description_i18n)), 599 GNUNET_JSON_pack_string ("unit", 600 pd->unit), 601 TALER_JSON_pack_amount_array ("unit_prices", 602 pd->price_array_length, 603 pd->price_array), 604 GNUNET_JSON_pack_bool ("unit_allow_fraction", 605 pd->allow_fractional_quantity), 606 GNUNET_JSON_pack_uint64 ("unit_precision_level", 607 pd->fractional_precision_level), 608 GNUNET_JSON_pack_string ("remaining_stock", 609 remaining_stock_buf), 610 GNUNET_JSON_pack_array_steal ("categories", 611 jcategories), 612 GNUNET_JSON_pack_allow_null ( 613 GNUNET_JSON_pack_array_incref ("taxes", 614 pd->taxes)), 615 GNUNET_JSON_pack_allow_null ( 616 GNUNET_JSON_pack_string ("image_hash", 617 pd->image_hash))); 618 619 /* Adding per product unit */ 620 TMH_unit_set_add (&ipc->unit_set, 621 pd->unit); 622 623 GNUNET_assert (0 == 624 json_array_append_new (ipc->products, 625 product)); 626 } 627 628 629 /** 630 * Add an inventory category to the payload if referenced. 631 * 632 * @param cls category payload context 633 * @param category_id category identifier 634 * @param category_name category name 635 * @param category_name_i18n translated names 636 * @param product_count number of products (unused) 637 */ 638 static void 639 add_inventory_category (void *cls, 640 uint64_t category_id, 641 const char *category_name, 642 const json_t *category_name_i18n, 643 uint64_t product_count) 644 { 645 struct InventoryPayloadContext *ipc = cls; 646 json_t *category; 647 648 (void) product_count; 649 category = GNUNET_JSON_PACK ( 650 GNUNET_JSON_pack_uint64 ("category_id", 651 category_id), 652 GNUNET_JSON_pack_string ("category_name", 653 category_name), 654 GNUNET_JSON_pack_allow_null ( 655 GNUNET_JSON_pack_object_incref ("category_name_i18n", 656 (json_t *) category_name_i18n))); 657 GNUNET_assert (0 == 658 json_array_append_new (ipc->category_payload, 659 category)); 660 } 661 662 663 /** 664 * Add an inventory unit to the payload if referenced and non-builtin. 665 * 666 * @param cls unit payload context 667 * @param unit_serial unit identifier 668 * @param ud unit details 669 */ 670 static void 671 add_inventory_unit (void *cls, 672 uint64_t unit_serial, 673 const struct TALER_MERCHANTDB_UnitDetails *ud) 674 { 675 struct InventoryPayloadContext *ipc = cls; 676 json_t *unit; 677 678 (void) unit_serial; 679 680 unit = GNUNET_JSON_PACK ( 681 GNUNET_JSON_pack_string ("unit", 682 ud->unit), 683 GNUNET_JSON_pack_string ("unit_name_long", 684 ud->unit_name_long), 685 GNUNET_JSON_pack_allow_null ( 686 GNUNET_JSON_pack_object_incref ("unit_name_long_i18n", 687 ud->unit_name_long_i18n)), 688 GNUNET_JSON_pack_string ("unit_name_short", 689 ud->unit_name_short), 690 GNUNET_JSON_pack_allow_null ( 691 GNUNET_JSON_pack_object_incref ("unit_name_short_i18n", 692 ud->unit_name_short_i18n)), 693 GNUNET_JSON_pack_bool ("unit_allow_fraction", 694 ud->unit_allow_fraction), 695 GNUNET_JSON_pack_uint64 ("unit_precision_level", 696 ud->unit_precision_level)); 697 GNUNET_assert (0 == 698 json_array_append_new (ipc->unit_payload, 699 unit)); 700 } 701 702 703 /** 704 * Build wallet-facing payload for inventory templates. 705 * 706 * @param connection HTTP connection 707 * @param mi merchant instance 708 * @param tp template details 709 * @return newly allocated template contract, NULL if an error was queued 710 */ 711 static json_t * 712 build_inventory_template_contract ( 713 struct MHD_Connection *connection, 714 const struct TMH_MerchantInstance *mi, 715 const struct TALER_MERCHANTDB_TemplateDetails *tp) 716 { 717 struct InventoryPayloadContext ipc; 718 const char **product_ids = NULL; 719 uint64_t *category_ids = NULL; 720 size_t num_product_ids = 0; 721 size_t num_category_ids = 0; 722 json_t *inventory_payload; 723 json_t *template_contract; 724 725 memset (&ipc, 726 0, 727 sizeof (ipc)); 728 ipc.products = json_array (); 729 { 730 struct GNUNET_JSON_Specification spec[] = { 731 GNUNET_JSON_spec_mark_optional ( 732 GNUNET_JSON_spec_array_const ("selected_categories", 733 &ipc.selected_categories), 734 NULL), 735 GNUNET_JSON_spec_mark_optional ( 736 GNUNET_JSON_spec_array_const ("selected_products", 737 &ipc.selected_products), 738 NULL), 739 GNUNET_JSON_spec_mark_optional ( 740 GNUNET_JSON_spec_bool ("selected_all", 741 &ipc.selected_all), 742 NULL), 743 GNUNET_JSON_spec_end () 744 }; 745 const char *err_name; 746 unsigned int err_line; 747 748 if (GNUNET_OK != 749 GNUNET_JSON_parse (tp->template_contract, 750 spec, 751 &err_name, 752 &err_line)) 753 { 754 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 755 "Invalid inventory template_contract for field %s\n", 756 err_name); 757 inventory_payload_cleanup (&ipc); 758 (void) TALER_MHD_reply_with_error ( 759 connection, 760 MHD_HTTP_INTERNAL_SERVER_ERROR, 761 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 762 err_name); 763 return NULL; 764 } 765 } 766 767 if (! ipc.selected_all) 768 { 769 if (NULL != ipc.selected_products) 770 { 771 size_t max_ids; 772 773 max_ids = json_array_size (ipc.selected_products); 774 if (0 < max_ids) 775 product_ids = GNUNET_new_array (max_ids, 776 const char *); 777 for (size_t i = 0; i < max_ids; i++) 778 { 779 const json_t *entry = json_array_get (ipc.selected_products, 780 i); 781 782 if (json_is_string (entry)) 783 product_ids[num_product_ids++] = json_string_value (entry); 784 else 785 GNUNET_break (0); 786 } 787 } 788 if (NULL != ipc.selected_categories) 789 { 790 size_t max_categories; 791 792 max_categories = json_array_size (ipc.selected_categories); 793 if (0 < max_categories) 794 category_ids = GNUNET_new_array (max_categories, 795 uint64_t); 796 for (size_t i = 0; i < max_categories; i++) 797 { 798 const json_t *entry = json_array_get (ipc.selected_categories, 799 i); 800 801 if (json_is_integer (entry) && 802 (0 < json_integer_value (entry))) 803 category_ids[num_category_ids++] 804 = (uint64_t) json_integer_value (entry); 805 else 806 GNUNET_break (0); 807 } 808 } 809 } 810 811 if (ipc.selected_all) 812 { 813 enum GNUNET_DB_QueryStatus qs; 814 815 qs = TALER_MERCHANTDB_iterate_inventory_products (TMH_db, 816 mi->settings.id, 817 &add_inventory_product, 818 &ipc); 819 if (0 > qs) 820 { 821 GNUNET_break (0); 822 inventory_payload_cleanup (&ipc); 823 (void) TALER_MHD_reply_with_error ( 824 connection, 825 MHD_HTTP_INTERNAL_SERVER_ERROR, 826 TALER_EC_GENERIC_DB_FETCH_FAILED, 827 "iterate_inventory_products"); 828 return NULL; 829 } 830 } 831 else if ( (0 < num_product_ids) || 832 (0 < num_category_ids) ) 833 { 834 enum GNUNET_DB_QueryStatus qs; 835 836 qs = TALER_MERCHANTDB_iterate_inventory_products_filtered ( 837 TMH_db, 838 mi->settings.id, 839 product_ids, 840 num_product_ids, 841 category_ids, 842 num_category_ids, 843 &add_inventory_product, 844 &ipc); 845 if (0 > qs) 846 { 847 GNUNET_break (0); 848 inventory_payload_cleanup (&ipc); 849 GNUNET_free (product_ids); 850 GNUNET_free (category_ids); 851 (void) TALER_MHD_reply_with_error ( 852 connection, 853 MHD_HTTP_INTERNAL_SERVER_ERROR, 854 TALER_EC_GENERIC_DB_FETCH_FAILED, 855 "iterate_inventory_products_filtered"); 856 return NULL; 857 } 858 } 859 GNUNET_free (product_ids); 860 GNUNET_free (category_ids); 861 862 ipc.category_payload = json_array (); 863 GNUNET_assert (NULL != ipc.category_payload); 864 if (0 < ipc.category_set.len) 865 { 866 enum GNUNET_DB_QueryStatus qs; 867 868 qs = TALER_MERCHANTDB_iterate_categories_by_ids ( 869 TMH_db, 870 mi->settings.id, 871 ipc.category_set.ids, 872 ipc.category_set.len, 873 &add_inventory_category, 874 &ipc); 875 if (0 > qs) 876 { 877 GNUNET_break (0); 878 inventory_payload_cleanup (&ipc); 879 (void) TALER_MHD_reply_with_error ( 880 connection, 881 MHD_HTTP_INTERNAL_SERVER_ERROR, 882 TALER_EC_GENERIC_DB_FETCH_FAILED, 883 "iterate_categories_by_ids"); 884 return NULL; 885 } 886 } 887 888 ipc.unit_payload = json_array (); 889 GNUNET_assert (NULL != ipc.unit_payload); 890 if (0 < ipc.unit_set.len) 891 { 892 enum GNUNET_DB_QueryStatus qs; 893 894 qs = TALER_MERCHANTDB_iterate_custom_units_by_names ( 895 TMH_db, 896 mi->settings.id, 897 (const char *const *) ipc.unit_set.units, 898 ipc.unit_set.len, 899 &add_inventory_unit, 900 &ipc); 901 if (0 > qs) 902 { 903 GNUNET_break (0); 904 inventory_payload_cleanup (&ipc); 905 (void) TALER_MHD_reply_with_error ( 906 connection, 907 MHD_HTTP_INTERNAL_SERVER_ERROR, 908 TALER_EC_GENERIC_DB_FETCH_FAILED, 909 "iterate_custom_units_by_names"); 910 return NULL; 911 } 912 } 913 914 inventory_payload = GNUNET_JSON_PACK ( 915 GNUNET_JSON_pack_array_steal ("products", 916 ipc.products), 917 GNUNET_JSON_pack_array_steal ("categories", 918 ipc.category_payload), 919 GNUNET_JSON_pack_array_steal ("units", 920 ipc.unit_payload)); 921 ipc.products = NULL; 922 ipc.category_payload = NULL; 923 ipc.unit_payload = NULL; 924 925 template_contract = json_deep_copy (tp->template_contract); 926 GNUNET_assert (NULL != template_contract); 927 /* remove internal fields */ 928 (void) json_object_del (template_contract, 929 "selected_categories"); 930 (void) json_object_del (template_contract, 931 "selected_products"); 932 (void) json_object_del (template_contract, 933 "selected_all"); 934 /* add inventory data */ 935 GNUNET_assert (0 == 936 json_object_set_new (template_contract, 937 "inventory_payload", 938 inventory_payload)); 939 inventory_payload_cleanup (&ipc); 940 return template_contract; 941 } 942 943 944 enum MHD_Result 945 TMH_get_templates_ID ( 946 const struct TMH_RequestHandler *rh, 947 struct MHD_Connection *connection, 948 struct TMH_HandlerContext *hc) 949 { 950 struct GetTemplateContext *gtc = hc->ctx; 951 struct TMH_MerchantInstance *mi = hc->instance; 952 953 (void) rh; 954 GNUNET_assert (NULL != mi); 955 if (NULL == gtc) 956 { 957 struct TALER_MERCHANTDB_TemplateDetails tp = { 0 }; 958 enum GNUNET_DB_QueryStatus qs; 959 960 gtc = GNUNET_new (struct GetTemplateContext); 961 gtc->hc = hc; 962 gtc->connection = connection; 963 gtc->exchange_candidates = json_array (); 964 GNUNET_assert (NULL != gtc->exchange_candidates); 965 gtc->merchant = TMH_instance_metadata_to_json (mi); 966 GNUNET_assert (NULL != gtc->merchant); 967 gtc->merchant_pub = mi->merchant_pub; 968 hc->ctx = gtc; 969 hc->cc = &get_template_cleanup; 970 971 qs = TALER_MERCHANTDB_get_template (TMH_db, 972 mi->settings.id, 973 hc->infix, 974 &tp); 975 if (0 > qs) 976 { 977 GNUNET_break (0); 978 return TALER_MHD_reply_with_error ( 979 connection, 980 MHD_HTTP_INTERNAL_SERVER_ERROR, 981 TALER_EC_GENERIC_DB_FETCH_FAILED, 982 "get_template"); 983 } 984 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 985 { 986 return TALER_MHD_reply_with_error ( 987 connection, 988 MHD_HTTP_NOT_FOUND, 989 TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN, 990 hc->infix); 991 } 992 if (NULL != tp.editable_defaults) 993 gtc->editable_defaults = json_incref (tp.editable_defaults); 994 switch (TALER_MERCHANT_template_type_from_contract (tp.template_contract)) 995 { 996 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 997 gtc->template_contract = build_inventory_template_contract (connection, 998 mi, 999 &tp); 1000 break; 1001 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1002 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1003 gtc->template_contract = json_incref (tp.template_contract); 1004 break; 1005 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1006 GNUNET_break_op (0); 1007 (void) TALER_MHD_reply_with_error ( 1008 connection, 1009 MHD_HTTP_INTERNAL_SERVER_ERROR, 1010 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1011 "template_type"); 1012 break; 1013 } 1014 TALER_MERCHANTDB_template_details_free (&tp); 1015 if (NULL == gtc->template_contract) 1016 return MHD_YES; 1017 1018 TMH_exchange_get_trusted (&start_exchange_lookup, 1019 gtc); 1020 if (NULL != gtc->lookup_head) 1021 { 1022 gtc->timeout_task = GNUNET_SCHEDULER_add_delayed ( 1023 MAX_KEYS_WAIT, 1024 &exchange_lookup_timeout, 1025 gtc); 1026 GNUNET_CONTAINER_DLL_insert (gtc_head, 1027 gtc_tail, 1028 gtc); 1029 gtc->suspended = GNUNET_YES; 1030 MHD_suspend_connection (connection); 1031 return MHD_YES; 1032 } 1033 } 1034 if (GNUNET_SYSERR == gtc->suspended) 1035 return MHD_NO; 1036 return TALER_MHD_REPLY_JSON_PACK ( 1037 connection, 1038 MHD_HTTP_OK, 1039 GNUNET_JSON_pack_object_incref ("template_contract", 1040 gtc->template_contract), 1041 GNUNET_JSON_pack_allow_null ( 1042 GNUNET_JSON_pack_object_incref ("editable_defaults", 1043 gtc->editable_defaults)), 1044 GNUNET_JSON_pack_allow_null ( 1045 GNUNET_JSON_pack_string ("required_currency", 1046 get_required_currency ( 1047 gtc->template_contract))), 1048 GNUNET_JSON_pack_object_incref ("merchant", 1049 gtc->merchant), 1050 GNUNET_JSON_pack_data_auto ("merchant_pub", 1051 >c->merchant_pub), 1052 GNUNET_JSON_pack_array_incref ("exchange_candidates", 1053 gtc->exchange_candidates)); 1054 } 1055 1056 1057 /* end of taler-merchant-httpd_get-templates-TEMPLATE_ID.c */