taler-merchant-httpd_post-templates-TEMPLATE_ID.c (48832B)
1 /* 2 This file is part of TALER 3 (C) 2022-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify 6 it under the terms of the GNU Affero General Public License as 7 published by the Free Software Foundation; either version 3, 8 or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with TALER; see the file COPYING. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 20 /** 21 * @file src/backend/taler-merchant-httpd_post-templates-TEMPLATE_ID.c 22 * @brief implementing POST /using-templates request handling 23 * @author Priscilla HUANG 24 * @author Christian Grothoff 25 */ 26 #include "platform.h" 27 #include "taler-merchant-httpd_exchanges.h" 28 #include "taler-merchant-httpd_post-templates-TEMPLATE_ID.h" 29 #include "taler-merchant-httpd_post-private-orders.h" 30 #include "taler-merchant-httpd_helper.h" 31 #include "taler-merchant-httpd_get-exchanges.h" 32 #include "taler/taler_merchant_util.h" 33 #include <taler/taler_json_lib.h> 34 #include <regex.h> 35 #include "merchant-database/lookup_product.h" 36 #include "merchant-database/lookup_template.h" 37 38 39 /** 40 * Item selected from inventory_selection. 41 */ 42 struct InventoryTemplateItemContext 43 { 44 /** 45 * Product ID as referenced in inventory. 46 */ 47 const char *product_id; 48 49 /** 50 * Unit quantity string as provided by the client. 51 */ 52 const char *unit_quantity; 53 54 /** 55 * Parsed integer quantity. 56 */ 57 uint64_t quantity_value; 58 59 /** 60 * Parsed fractional quantity. 61 */ 62 uint32_t quantity_frac; 63 64 /** 65 * Product details from the DB (includes price array). 66 */ 67 struct TALER_MERCHANTDB_ProductDetails pd; 68 69 /** 70 * Categories referenced by the product. 71 */ 72 uint64_t *categories; 73 74 /** 75 * Length of @e categories. 76 */ 77 size_t num_categories; 78 }; 79 80 81 /** 82 * Our context. 83 */ 84 enum UsePhase 85 { 86 /** 87 * Parse request payload into context fields. 88 */ 89 USE_PHASE_PARSE_REQUEST, 90 91 /** 92 * Fetch template details from the database. 93 */ 94 USE_PHASE_LOOKUP_TEMPLATE, 95 96 /** 97 * Parse template. 98 */ 99 USE_PHASE_PARSE_TEMPLATE, 100 101 /** 102 * Load additional details (like products and 103 * categories) needed for verification and 104 * price computation. 105 */ 106 USE_PHASE_DB_FETCH, 107 108 /** 109 * Validate request and template compatibility. 110 */ 111 USE_PHASE_VERIFY, 112 113 /** 114 * Compute price of the order. 115 */ 116 USE_PHASE_COMPUTE_PRICE, 117 118 /** 119 * Handle tip. 120 */ 121 USE_PHASE_CHECK_TIP, 122 123 /** 124 * Check if client-supplied total amount matches 125 * our calculation (if we did any). 126 */ 127 USE_PHASE_CHECK_TOTAL, 128 129 /** 130 * Construct the internal order request body. 131 */ 132 USE_PHASE_CREATE_ORDER, 133 134 /** 135 * Submit the order to the shared order handler. 136 */ 137 USE_PHASE_SUBMIT_ORDER, 138 139 /** 140 * Finished successfully with MHD_YES. 141 */ 142 USE_PHASE_FINISHED_MHD_YES, 143 144 /** 145 * Finished with MHD_NO. 146 */ 147 USE_PHASE_FINISHED_MHD_NO 148 }; 149 150 struct UseContext 151 { 152 /** 153 * Context for our handler. 154 */ 155 struct TMH_HandlerContext *hc; 156 157 /** 158 * Internal handler context we are passing into the 159 * POST /private/orders handler. 160 */ 161 struct TMH_HandlerContext ihc; 162 163 /** 164 * Phase we are currently in. 165 */ 166 enum UsePhase phase; 167 168 /** 169 * Template type from the contract. 170 */ 171 enum TALER_MERCHANT_TemplateType template_type; 172 173 /** 174 * Information set in the #USE_PHASE_PARSE_REQUEST phase. 175 */ 176 struct 177 { 178 /** 179 * Summary override from request, if any. 180 */ 181 const char *summary; 182 183 /** 184 * Amount provided by the client. 185 */ 186 struct TALER_Amount amount; 187 188 /** 189 * Tip provided by the client. 190 */ 191 struct TALER_Amount tip; 192 193 /** 194 * True if @e amount was not provided. 195 */ 196 bool no_amount; 197 198 /** 199 * True if @e tip was not provided. 200 */ 201 bool no_tip; 202 203 /** 204 * Parsed fields for inventory templates. 205 */ 206 struct 207 { 208 /** 209 * Selected products from inventory_selection. 210 */ 211 struct InventoryTemplateItemContext *items; 212 213 /** 214 * Length of @e items. 215 */ 216 unsigned int items_len; 217 218 } inventory; 219 220 /** 221 * Request details if this is a paivana instantiation. 222 */ 223 struct 224 { 225 226 /** 227 * Target website for the request. 228 */ 229 const char *website; 230 231 /** 232 * Unique client identifier, consisting of 233 * current time, "-", and the hash of a nonce, 234 * the website and the current time. 235 */ 236 const char *paivana_id; 237 238 } paivana; 239 240 } parse_request; 241 242 /** 243 * Information set in the #USE_PHASE_LOOKUP_TEMPLATE phase. 244 */ 245 struct 246 { 247 248 /** 249 * Our template details from the DB. 250 */ 251 struct TALER_MERCHANTDB_TemplateDetails etp; 252 253 } lookup_template; 254 255 /** 256 * Information set in the #USE_PHASE_PARSE_TEMPLATE phase. 257 */ 258 struct TALER_MERCHANT_TemplateContract template_contract; 259 260 /** 261 * Information set in the #USE_PHASE_COMPUTE_PRICE phase. 262 */ 263 struct 264 { 265 266 /** 267 * Per-currency totals across selected products (without tips). 268 */ 269 struct TALER_Amount *totals; 270 271 /** 272 * Length of @e totals. 273 */ 274 unsigned int totals_len; 275 276 /** 277 * Array of payment choices, used with Paviana. 278 */ 279 json_t *choices; 280 281 } compute_price; 282 283 }; 284 285 286 /** 287 * Clean up inventory items. 288 * 289 * @param items_len length of @a items 290 * @param[in] items item array to free 291 */ 292 static void 293 cleanup_inventory_items (unsigned int items_len, 294 struct InventoryTemplateItemContext items[static 295 items_len]) 296 { 297 for (unsigned int i = 0; i < items_len; i++) 298 { 299 struct InventoryTemplateItemContext *item = &items[i]; 300 301 TALER_MERCHANTDB_product_details_free (&item->pd); 302 GNUNET_free (item->categories); 303 } 304 GNUNET_free (items); 305 } 306 307 308 /** 309 * Clean up a `struct UseContext *` 310 * 311 * @param[in] cls a `struct UseContext *` 312 */ 313 static void 314 cleanup_use_context (void *cls) 315 { 316 struct UseContext *uc = cls; 317 318 TALER_MERCHANTDB_template_details_free (&uc->lookup_template.etp); 319 if (NULL != 320 uc->parse_request.inventory.items) 321 cleanup_inventory_items (uc->parse_request.inventory.items_len, 322 uc->parse_request.inventory.items); 323 GNUNET_free (uc->compute_price.totals); 324 uc->compute_price.totals_len = 0; 325 json_decref (uc->compute_price.choices); 326 if (NULL != uc->ihc.cc) 327 uc->ihc.cc (uc->ihc.ctx); 328 GNUNET_free (uc->ihc.infix); 329 json_decref (uc->ihc.request_body); 330 GNUNET_free (uc); 331 } 332 333 334 /** 335 * Finalize a template use request. 336 * 337 * @param[in,out] uc use context 338 * @param ret handler return value 339 */ 340 static void 341 use_finalize (struct UseContext *uc, 342 enum MHD_Result ret) 343 { 344 uc->phase = (MHD_YES == ret) 345 ? USE_PHASE_FINISHED_MHD_YES 346 : USE_PHASE_FINISHED_MHD_NO; 347 } 348 349 350 /** 351 * Finalize after JSON parsing result. 352 * 353 * @param[in,out] uc use context 354 * @param res parse result 355 */ 356 static void 357 use_finalize_parse (struct UseContext *uc, 358 enum GNUNET_GenericReturnValue res) 359 { 360 GNUNET_assert (GNUNET_OK != res); 361 use_finalize (uc, 362 (GNUNET_NO == res) 363 ? MHD_YES 364 : MHD_NO); 365 } 366 367 368 /** 369 * Reply with error and finalize the request. 370 * 371 * @param[in,out] uc use context 372 * @param http_status HTTP status code 373 * @param ec error code 374 * @param detail error detail 375 */ 376 static void 377 use_reply_with_error (struct UseContext *uc, 378 unsigned int http_status, 379 enum TALER_ErrorCode ec, 380 const char *detail) 381 { 382 enum MHD_Result mret; 383 384 mret = TALER_MHD_reply_with_error (uc->hc->connection, 385 http_status, 386 ec, 387 detail); 388 use_finalize (uc, 389 mret); 390 } 391 392 393 /* ***************** USE_PHASE_PARSE_REQUEST **************** */ 394 395 /** 396 * Parse request data for inventory templates. 397 * 398 * @param[in,out] uc use context 399 * @return #GNUNET_OK on success 400 */ 401 static enum GNUNET_GenericReturnValue 402 parse_using_templates_inventory_request ( 403 struct UseContext *uc) 404 { 405 const json_t *inventory_selection; 406 struct GNUNET_JSON_Specification spec[] = { 407 GNUNET_JSON_spec_array_const ("inventory_selection", 408 &inventory_selection), 409 GNUNET_JSON_spec_end () 410 }; 411 enum GNUNET_GenericReturnValue res; 412 413 GNUNET_assert (NULL == uc->ihc.request_body); 414 res = TALER_MHD_parse_json_data (uc->hc->connection, 415 uc->hc->request_body, 416 spec); 417 if (GNUNET_OK != res) 418 { 419 GNUNET_break_op (0); 420 use_finalize_parse (uc, 421 res); 422 return GNUNET_SYSERR; 423 } 424 425 if ( (! uc->parse_request.no_amount) && 426 (! TMH_test_exchange_configured_for_currency ( 427 uc->parse_request.amount.currency)) ) 428 { 429 GNUNET_break_op (0); 430 use_reply_with_error (uc, 431 MHD_HTTP_CONFLICT, 432 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 433 "Currency is not supported by backend"); 434 return GNUNET_SYSERR; 435 } 436 437 for (size_t i = 0; i < json_array_size (inventory_selection); i++) 438 { 439 struct InventoryTemplateItemContext item = { 0 }; 440 struct GNUNET_JSON_Specification ispec[] = { 441 GNUNET_JSON_spec_string ("product_id", 442 &item.product_id), 443 GNUNET_JSON_spec_string ("quantity", 444 &item.unit_quantity), 445 GNUNET_JSON_spec_end () 446 }; 447 const char *err_name; 448 unsigned int err_line; 449 450 res = GNUNET_JSON_parse (json_array_get (inventory_selection, 451 i), 452 ispec, 453 &err_name, 454 &err_line); 455 if (GNUNET_OK != res) 456 { 457 GNUNET_break_op (0); 458 use_reply_with_error (uc, 459 MHD_HTTP_BAD_REQUEST, 460 TALER_EC_GENERIC_PARAMETER_MALFORMED, 461 "inventory_selection"); 462 return GNUNET_SYSERR; 463 } 464 465 GNUNET_array_append (uc->parse_request.inventory.items, 466 uc->parse_request.inventory.items_len, 467 item); 468 } 469 return GNUNET_OK; 470 } 471 472 473 /** 474 * Parse request data for paivana templates. 475 * 476 * @param[in,out] uc use context 477 * @return #GNUNET_OK on success 478 */ 479 static enum GNUNET_GenericReturnValue 480 parse_using_templates_paivana_request ( 481 struct UseContext *uc) 482 { 483 struct GNUNET_JSON_Specification spec[] = { 484 GNUNET_JSON_spec_string ("website", 485 &uc->parse_request.paivana.website), 486 GNUNET_JSON_spec_string ("paivana_id", 487 &uc->parse_request.paivana.paivana_id), 488 GNUNET_JSON_spec_end () 489 }; 490 enum GNUNET_GenericReturnValue res; 491 struct GNUNET_HashCode sh; 492 unsigned long long tv; 493 const char *dash; 494 495 GNUNET_assert (NULL == uc->ihc.request_body); 496 res = TALER_MHD_parse_json_data (uc->hc->connection, 497 uc->hc->request_body, 498 spec); 499 if (GNUNET_OK != res) 500 { 501 GNUNET_break_op (0); 502 use_finalize_parse (uc, 503 res); 504 return GNUNET_SYSERR; 505 } 506 if (1 != 507 sscanf (uc->parse_request.paivana.paivana_id, 508 "%llu-", 509 &tv)) 510 { 511 GNUNET_break_op (0); 512 use_reply_with_error (uc, 513 MHD_HTTP_BAD_REQUEST, 514 TALER_EC_GENERIC_PARAMETER_MALFORMED, 515 "paivana_id"); 516 return GNUNET_SYSERR; 517 } 518 dash = strchr (uc->parse_request.paivana.paivana_id, 519 '-'); 520 GNUNET_assert (NULL != dash); 521 if (GNUNET_OK != 522 GNUNET_STRINGS_string_to_data (dash + 1, 523 strlen (dash + 1), 524 &sh, 525 sizeof (sh))) 526 { 527 GNUNET_break_op (0); 528 use_reply_with_error (uc, 529 MHD_HTTP_BAD_REQUEST, 530 TALER_EC_GENERIC_PARAMETER_MALFORMED, 531 "paivana_id"); 532 return GNUNET_SYSERR; 533 } 534 return GNUNET_OK; 535 } 536 537 538 /** 539 * Main function for the #USE_PHASE_PARSE_REQUEST. 540 * 541 * @param[in,out] uc context to update 542 */ 543 static void 544 handle_phase_parse_request ( 545 struct UseContext *uc) 546 { 547 const char *template_type = NULL; 548 struct GNUNET_JSON_Specification spec[] = { 549 GNUNET_JSON_spec_mark_optional ( 550 GNUNET_JSON_spec_string ("template_type", 551 &template_type), 552 NULL), 553 GNUNET_JSON_spec_mark_optional ( 554 TALER_JSON_spec_amount_any ("tip", 555 &uc->parse_request.tip), 556 &uc->parse_request.no_tip), 557 GNUNET_JSON_spec_mark_optional ( 558 GNUNET_JSON_spec_string ("summary", 559 &uc->parse_request.summary), 560 NULL), 561 GNUNET_JSON_spec_mark_optional ( 562 TALER_JSON_spec_amount_any ("amount", 563 &uc->parse_request.amount), 564 &uc->parse_request.no_amount), 565 GNUNET_JSON_spec_end () 566 }; 567 enum GNUNET_GenericReturnValue res; 568 569 res = TALER_MHD_parse_json_data (uc->hc->connection, 570 uc->hc->request_body, 571 spec); 572 if (GNUNET_OK != res) 573 { 574 GNUNET_break_op (0); 575 use_finalize_parse (uc, 576 res); 577 return; 578 } 579 if (NULL == template_type) 580 template_type = "fixed-order"; 581 uc->template_type 582 = TALER_MERCHANT_template_type_from_string ( 583 template_type); 584 switch (uc->template_type) 585 { 586 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 587 /* nothig left to do */ 588 uc->phase++; 589 return; 590 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 591 res = parse_using_templates_paivana_request (uc); 592 if (GNUNET_OK == res) 593 uc->phase++; 594 return; 595 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 596 res = parse_using_templates_inventory_request (uc); 597 if (GNUNET_OK == res) 598 uc->phase++; 599 return; 600 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 601 break; 602 } 603 GNUNET_break (0); 604 use_reply_with_error ( 605 uc, 606 MHD_HTTP_BAD_REQUEST, 607 TALER_EC_GENERIC_PARAMETER_MALFORMED, 608 "template_type"); 609 } 610 611 612 /* ***************** USE_PHASE_LOOKUP_TEMPLATE **************** */ 613 614 /** 615 * Main function for the #USE_PHASE_LOOKUP_TEMPLATE. 616 * 617 * @param[in,out] uc context to update 618 */ 619 static void 620 handle_phase_lookup_template ( 621 struct UseContext *uc) 622 { 623 struct TMH_MerchantInstance *mi = uc->hc->instance; 624 const char *template_id = uc->hc->infix; 625 enum GNUNET_DB_QueryStatus qs; 626 627 qs = TALER_MERCHANTDB_lookup_template (TMH_db, 628 mi->settings.id, 629 template_id, 630 &uc->lookup_template.etp); 631 switch (qs) 632 { 633 case GNUNET_DB_STATUS_HARD_ERROR: 634 /* Clean up and fail hard */ 635 GNUNET_break (0); 636 use_reply_with_error (uc, 637 MHD_HTTP_INTERNAL_SERVER_ERROR, 638 TALER_EC_GENERIC_DB_FETCH_FAILED, 639 "lookup_template"); 640 return; 641 case GNUNET_DB_STATUS_SOFT_ERROR: 642 /* this should be impossible (single select) */ 643 GNUNET_break (0); 644 use_reply_with_error (uc, 645 MHD_HTTP_INTERNAL_SERVER_ERROR, 646 TALER_EC_GENERIC_DB_FETCH_FAILED, 647 "lookup_template"); 648 return; 649 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 650 /* template not found! */ 651 use_reply_with_error (uc, 652 MHD_HTTP_NOT_FOUND, 653 TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN, 654 template_id); 655 return; 656 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 657 /* all good */ 658 break; 659 } 660 if (uc->template_type != 661 TALER_MERCHANT_template_type_from_contract ( 662 uc->lookup_template.etp.template_contract)) 663 { 664 GNUNET_break_op (0); 665 use_reply_with_error ( 666 uc, 667 MHD_HTTP_CONFLICT, 668 TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_TYPE, 669 "template_contract has different type"); 670 return; 671 } 672 uc->phase++; 673 } 674 675 676 /* ***************** USE_PHASE_PARSE_TEMPLATE **************** */ 677 678 679 /** 680 * Parse template. 681 * 682 * @param[in,out] uc use context 683 */ 684 static void 685 handle_phase_template_contract (struct UseContext *uc) 686 { 687 const char *err_name; 688 enum GNUNET_GenericReturnValue res; 689 690 res = TALER_MERCHANT_template_contract_parse ( 691 uc->lookup_template.etp.template_contract, 692 &uc->template_contract, 693 &err_name); 694 if (GNUNET_OK != res) 695 { 696 GNUNET_break (0); 697 use_reply_with_error (uc, 698 MHD_HTTP_INTERNAL_SERVER_ERROR, 699 TALER_EC_GENERIC_DB_FETCH_FAILED, 700 err_name); 701 return; 702 } 703 uc->phase++; 704 } 705 706 707 /* ***************** USE_PHASE_DB_FETCH **************** */ 708 709 /** 710 * Fetch DB data for inventory templates. 711 * 712 * @param[in,out] uc use context 713 */ 714 static void 715 handle_phase_db_fetch (struct UseContext *uc) 716 { 717 struct TMH_MerchantInstance *mi = uc->hc->instance; 718 719 switch (uc->template_type) 720 { 721 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 722 uc->phase++; 723 return; 724 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 725 uc->phase++; 726 return; 727 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 728 break; 729 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 730 GNUNET_assert (0); 731 } 732 733 for (unsigned int i = 0; 734 i < uc->parse_request.inventory.items_len; 735 i++) 736 { 737 struct InventoryTemplateItemContext *item = 738 &uc->parse_request.inventory.items[i]; 739 enum GNUNET_DB_QueryStatus qs; 740 741 qs = TALER_MERCHANTDB_lookup_product (TMH_db, 742 mi->settings.id, 743 item->product_id, 744 &item->pd, 745 &item->num_categories, 746 &item->categories); 747 switch (qs) 748 { 749 case GNUNET_DB_STATUS_HARD_ERROR: 750 GNUNET_break (0); 751 use_reply_with_error (uc, 752 MHD_HTTP_INTERNAL_SERVER_ERROR, 753 TALER_EC_GENERIC_DB_FETCH_FAILED, 754 "lookup_product"); 755 return; 756 case GNUNET_DB_STATUS_SOFT_ERROR: 757 GNUNET_break (0); 758 use_reply_with_error (uc, 759 MHD_HTTP_INTERNAL_SERVER_ERROR, 760 TALER_EC_GENERIC_DB_FETCH_FAILED, 761 "lookup_product"); 762 return; 763 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 764 use_reply_with_error (uc, 765 MHD_HTTP_NOT_FOUND, 766 TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN, 767 item->product_id); 768 return; 769 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 770 break; 771 } 772 } 773 uc->phase++; 774 } 775 776 777 /* *************** Helpers for USE_PHASE_VERIFY ***************** */ 778 779 /** 780 * Check if the given product ID appears in the array of allowed_products. 781 * 782 * @param allowed_products JSON array of product IDs allowed by the template, may be NULL 783 * @param product_id product ID to check 784 * @return true if the product ID is in the list 785 */ 786 static bool 787 product_id_allowed (const json_t *allowed_products, 788 const char *product_id) 789 { 790 const json_t *entry; 791 size_t idx; 792 793 if (NULL == allowed_products) 794 return false; 795 json_array_foreach ((json_t *) allowed_products, idx, entry) 796 { 797 if (! json_is_string (entry)) 798 { 799 GNUNET_break (0); 800 continue; 801 } 802 if (0 == strcmp (json_string_value (entry), 803 product_id)) 804 return true; 805 } 806 return false; 807 } 808 809 810 /** 811 * Check if any product category is in the selected_categories list. 812 * 813 * @param allowed_categories JSON array of categories allowed by the template, may be NULL 814 * @param num_categories length of @a categories 815 * @param categories list of categories of the selected product 816 * @return true if any category of the product is in the list of allowed categories matches 817 */ 818 static bool 819 category_allowed (const json_t *allowed_categories, 820 size_t num_categories, 821 const uint64_t categories[num_categories]) 822 { 823 const json_t *entry; 824 size_t idx; 825 826 if (NULL == allowed_categories) 827 return false; 828 json_array_foreach ((json_t *) allowed_categories, 829 idx, 830 entry) 831 { 832 uint64_t selected_id; 833 834 if (! json_is_integer (entry)) 835 { 836 GNUNET_break (0); 837 continue; 838 } 839 if (0 > json_integer_value (entry)) 840 { 841 GNUNET_break (0); 842 continue; 843 } 844 selected_id = (uint64_t) json_integer_value (entry); 845 for (size_t i = 0; i < num_categories; i++) 846 { 847 if (categories[i] == selected_id) 848 return true; 849 } 850 } 851 return false; 852 } 853 854 855 /** 856 * Verify request data for inventory templates. 857 * Checks that the selected products are allowed 858 * for this template. 859 * 860 * @param[in,out] uc use context 861 * @return #GNUNET_OK on success 862 */ 863 static enum GNUNET_GenericReturnValue 864 verify_using_templates_inventory (struct UseContext *uc) 865 { 866 if (uc->template_contract.details.inventory.choose_one && 867 (1 != uc->parse_request.inventory.items_len)) 868 { 869 GNUNET_break_op (0); 870 use_reply_with_error (uc, 871 MHD_HTTP_CONFLICT, 872 TALER_EC_GENERIC_PARAMETER_MALFORMED, 873 "inventory_selection"); 874 return GNUNET_SYSERR; 875 } 876 if (uc->template_contract.details.inventory.selected_all) 877 return GNUNET_OK; 878 for (unsigned int i = 0; 879 i < uc->parse_request.inventory.items_len; 880 i++) 881 { 882 struct InventoryTemplateItemContext *item = 883 &uc->parse_request.inventory.items[i]; 884 const char *eparam = NULL; 885 886 if (GNUNET_OK != 887 TALER_MERCHANT_vk_process_quantity_inputs ( 888 TALER_MERCHANT_VK_QUANTITY, 889 item->pd.allow_fractional_quantity, 890 true, 891 0, 892 false, 893 item->unit_quantity, 894 &item->quantity_value, 895 &item->quantity_frac, 896 &eparam)) 897 { 898 GNUNET_break_op (0); 899 use_reply_with_error (uc, 900 MHD_HTTP_BAD_REQUEST, 901 TALER_EC_GENERIC_PARAMETER_MALFORMED, 902 eparam); 903 return GNUNET_SYSERR; 904 } 905 906 if (0 == item->pd.price_array_length) 907 { 908 GNUNET_break (0); 909 use_reply_with_error (uc, 910 MHD_HTTP_INTERNAL_SERVER_ERROR, 911 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 912 "price_array"); 913 return GNUNET_SYSERR; 914 } 915 } 916 917 for (unsigned int i = 0; 918 i < uc->parse_request.inventory.items_len; 919 i++) 920 { 921 struct InventoryTemplateItemContext *item = 922 &uc->parse_request.inventory.items[i]; 923 924 if (product_id_allowed (uc->template_contract.details.inventory. 925 selected_products, 926 item->product_id)) 927 continue; 928 if (category_allowed ( 929 uc->template_contract.details.inventory.selected_categories, 930 item->num_categories, 931 item->categories)) 932 continue; 933 GNUNET_break_op (0); 934 use_reply_with_error ( 935 uc, 936 MHD_HTTP_CONFLICT, 937 TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_PRODUCT, 938 item->product_id); 939 return GNUNET_SYSERR; 940 } 941 return GNUNET_OK; 942 } 943 944 945 /** 946 * Verify request data for fixed-order templates. 947 * As here we cannot compute the total amount, either 948 * the template or the client request must provide it. 949 * 950 * @param[in,out] uc use context 951 * @return #GNUNET_OK on success 952 */ 953 static enum GNUNET_GenericReturnValue 954 verify_using_templates_fixed ( 955 struct UseContext *uc) 956 { 957 if ( (! uc->parse_request.no_amount) && 958 (! uc->template_contract.no_amount) ) 959 { 960 GNUNET_break_op (0); 961 use_reply_with_error (uc, 962 MHD_HTTP_CONFLICT, 963 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 964 NULL); 965 return GNUNET_SYSERR; 966 } 967 if (uc->parse_request.no_amount && 968 uc->template_contract.no_amount) 969 { 970 GNUNET_break_op (0); 971 use_reply_with_error (uc, 972 MHD_HTTP_CONFLICT, 973 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT, 974 NULL); 975 return GNUNET_SYSERR; 976 } 977 return GNUNET_OK; 978 } 979 980 981 /** 982 * Verify request data for paivana templates. 983 * 984 * @param[in,out] uc use context 985 * @return #GNUNET_OK on success 986 */ 987 static enum GNUNET_GenericReturnValue 988 verify_using_templates_paivana ( 989 struct UseContext *uc) 990 { 991 if (NULL != uc->template_contract.details.paivana.website_regex) 992 { 993 regex_t ex; 994 bool allowed = false; 995 996 if (0 != regcomp (&ex, 997 uc->template_contract.details.paivana.website_regex, 998 REG_NOSUB | REG_EXTENDED)) 999 { 1000 GNUNET_break_op (0); 1001 return GNUNET_SYSERR; 1002 } 1003 if (0 == 1004 regexec (&ex, 1005 uc->parse_request.paivana.website, 1006 0, NULL, 1007 0)) 1008 { 1009 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1010 "Website `%s' allowed by template\n", 1011 uc->parse_request.paivana.website); 1012 allowed = true; 1013 } 1014 regfree (&ex); 1015 if (! allowed) 1016 return GNUNET_SYSERR; 1017 } 1018 return GNUNET_OK; 1019 } 1020 1021 1022 /** 1023 * Verify that the client request is structurally acceptable for the specified 1024 * template. Does NOT check the total amount being reasonable. 1025 * 1026 * @param[in,out] uc use context 1027 */ 1028 static void 1029 handle_phase_verify ( 1030 struct UseContext *uc) 1031 { 1032 enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; 1033 1034 if ( (NULL != uc->parse_request.summary) && 1035 (NULL != uc->template_contract.summary) ) 1036 { 1037 GNUNET_break_op (0); 1038 use_reply_with_error (uc, 1039 MHD_HTTP_CONFLICT, 1040 TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT, 1041 NULL); 1042 return; 1043 } 1044 if ( (NULL == uc->parse_request.summary) && 1045 (NULL == uc->template_contract.summary) ) 1046 { 1047 GNUNET_break_op (0); 1048 use_reply_with_error (uc, 1049 MHD_HTTP_CONFLICT, 1050 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY, 1051 NULL); 1052 return; 1053 } 1054 if ( (! uc->parse_request.no_amount) && 1055 (NULL != uc->template_contract.currency) && 1056 (0 != strcasecmp (uc->template_contract.currency, 1057 uc->parse_request.amount.currency)) ) 1058 { 1059 GNUNET_break_op (0); 1060 use_reply_with_error (uc, 1061 MHD_HTTP_CONFLICT, 1062 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1063 uc->template_contract.currency); 1064 return; 1065 } 1066 switch (uc->template_type) 1067 { 1068 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1069 res = verify_using_templates_fixed (uc); 1070 break; 1071 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1072 res = verify_using_templates_paivana (uc); 1073 break; 1074 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 1075 res = verify_using_templates_inventory (uc); 1076 break; 1077 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1078 GNUNET_assert (0); 1079 } 1080 if (GNUNET_OK == res) 1081 uc->phase++; 1082 } 1083 1084 1085 /* ***************** USE_PHASE_COMPUTE_PRICE **************** */ 1086 1087 1088 /** 1089 * Compute the line total for a product based on quantity. 1090 * 1091 * @param unit_price price per unit 1092 * @param quantity integer quantity 1093 * @param quantity_frac fractional quantity (0..TALER_MERCHANT_UNIT_FRAC_BASE-1) 1094 * @param[out] line_total resulting line total 1095 * @return #GNUNET_OK on success 1096 */ 1097 static enum GNUNET_GenericReturnValue 1098 compute_line_total (const struct TALER_Amount *unit_price, 1099 uint64_t quantity, 1100 uint32_t quantity_frac, 1101 struct TALER_Amount *line_total) 1102 { 1103 struct TALER_Amount tmp; 1104 1105 GNUNET_assert (GNUNET_OK == 1106 TALER_amount_set_zero (unit_price->currency, 1107 line_total)); 1108 if ( (0 != quantity) && 1109 (0 > 1110 TALER_amount_multiply (line_total, 1111 unit_price, 1112 (uint32_t) quantity)) ) 1113 { 1114 GNUNET_break (0); 1115 return GNUNET_SYSERR; 1116 } 1117 if (0 == quantity_frac) 1118 return GNUNET_OK; 1119 if (0 > 1120 TALER_amount_multiply (&tmp, 1121 unit_price, 1122 quantity_frac)) 1123 { 1124 GNUNET_break (0); 1125 return GNUNET_SYSERR; 1126 } 1127 TALER_amount_divide (&tmp, 1128 &tmp, 1129 TALER_MERCHANT_UNIT_FRAC_BASE); 1130 if (0 > 1131 TALER_amount_add (line_total, 1132 line_total, 1133 &tmp)) 1134 { 1135 GNUNET_break (0); 1136 return GNUNET_SYSERR; 1137 } 1138 return GNUNET_OK; 1139 } 1140 1141 1142 /** 1143 * Find the price of the given @a item in the specified 1144 * @a currency. 1145 * 1146 * @param currency currency to search price in 1147 * @param item item to check prices of 1148 * @return NULL if a suitable price was not found 1149 */ 1150 static const struct TALER_Amount * 1151 find_item_price_in_currency ( 1152 const char *currency, 1153 const struct InventoryTemplateItemContext *item) 1154 { 1155 for (size_t j = 0; j < item->pd.price_array_length; j++) 1156 { 1157 if (0 == strcasecmp (item->pd.price_array[j].currency, 1158 currency)) 1159 return &item->pd.price_array[j]; 1160 } 1161 return NULL; 1162 } 1163 1164 1165 /** 1166 * Compute totals for all currencies shared across selected products. 1167 * 1168 * @param[in,out] uc use context 1169 * @return #GNUNET_OK on success (including no price due to no items) 1170 * #GNUNET_NO if we could not find a price in any accepted currency 1171 * for all selected products 1172 * #GNUNET_SYSERR on arithmetic issues (internal error) 1173 */ 1174 static enum GNUNET_GenericReturnValue 1175 compute_totals_per_currency (struct UseContext *uc) 1176 { 1177 const struct InventoryTemplateItemContext *items 1178 = uc->parse_request.inventory.items; 1179 unsigned int items_len = uc->parse_request.inventory.items_len; 1180 1181 if (0 == items_len) 1182 return GNUNET_OK; 1183 for (size_t i = 0; i < items[0].pd.price_array_length; i++) 1184 { 1185 const struct TALER_Amount *price 1186 = &items[0].pd.price_array[i]; 1187 struct TALER_Amount zero; 1188 1189 if (! TMH_test_exchange_configured_for_currency (price->currency)) 1190 continue; 1191 GNUNET_assert (GNUNET_OK == 1192 TALER_amount_set_zero (price->currency, 1193 &zero)); 1194 GNUNET_array_append (uc->compute_price.totals, 1195 uc->compute_price.totals_len, 1196 zero); 1197 } 1198 if (0 == uc->compute_price.totals_len) 1199 { 1200 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1201 "No currency supported by our configuration in which we have prices for first selected product!\n"); 1202 return GNUNET_NO; 1203 } 1204 /* Loop through items, ensure each currency exists and sum totals. */ 1205 for (unsigned int i = 0; i < items_len; i++) 1206 { 1207 const struct InventoryTemplateItemContext *item = &items[i]; 1208 unsigned int c = 0; 1209 1210 while (c < uc->compute_price.totals_len) 1211 { 1212 struct TALER_Amount *total = &uc->compute_price.totals[c]; 1213 const struct TALER_Amount *unit_price; 1214 struct TALER_Amount line_total; 1215 1216 unit_price = find_item_price_in_currency (total->currency, 1217 item); 1218 if (NULL == unit_price) 1219 { 1220 /* Drop the currency: we have no price in one of 1221 the selected products */ 1222 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1223 "Product `%s' has no price in %s: dropping currency\n", 1224 item->product_id, 1225 total->currency); 1226 *total = uc->compute_price.totals[--uc->compute_price.totals_len]; 1227 continue; 1228 } 1229 if (GNUNET_OK != 1230 compute_line_total (unit_price, 1231 item->quantity_value, 1232 item->quantity_frac, 1233 &line_total)) 1234 { 1235 GNUNET_break (0); 1236 return GNUNET_SYSERR; 1237 } 1238 if (0 > 1239 TALER_amount_add (total, 1240 total, 1241 &line_total)) 1242 { 1243 GNUNET_break (0); 1244 return GNUNET_SYSERR; 1245 } 1246 c++; 1247 } 1248 } 1249 if (0 == uc->compute_price.totals_len) 1250 { 1251 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1252 "No currency available in which we have prices for all selected products!\n"); 1253 GNUNET_free (uc->compute_price.totals); 1254 } 1255 return (0 == uc->compute_price.totals_len) 1256 ? GNUNET_NO 1257 : GNUNET_OK; 1258 } 1259 1260 1261 /** 1262 * Compute total for only the given @a currency. 1263 * 1264 * @param items_len length of @a items 1265 * @param items inventory items 1266 * @param currency currency to total 1267 * @param[out] total computed total 1268 * @return #GNUNET_OK on success 1269 * #GNUNET_NO if we could not find a price in any accepted currency 1270 * for all selected products 1271 * #GNUNET_SYSERR on arithmetic issues (internal error) 1272 */ 1273 static enum GNUNET_GenericReturnValue 1274 compute_inventory_total (unsigned int items_len, 1275 const struct InventoryTemplateItemContext *items, 1276 const char *currency, 1277 struct TALER_Amount *total) 1278 { 1279 GNUNET_assert (NULL != currency); 1280 GNUNET_assert (GNUNET_OK == 1281 TALER_amount_set_zero (currency, 1282 total)); 1283 for (unsigned int i = 0; i < items_len; i++) 1284 { 1285 const struct InventoryTemplateItemContext *item = &items[i]; 1286 const struct TALER_Amount *unit_price; 1287 struct TALER_Amount line_total; 1288 1289 unit_price = find_item_price_in_currency (currency, 1290 item); 1291 if (NULL == unit_price) 1292 { 1293 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1294 "compute_inventory_total: no price in %s for product `%s'\n", 1295 currency, 1296 item->product_id); 1297 return GNUNET_NO; 1298 } 1299 if (GNUNET_OK != 1300 compute_line_total (unit_price, 1301 item->quantity_value, 1302 item->quantity_frac, 1303 &line_total)) 1304 { 1305 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1306 "compute_inventory_total: line total failed for %s in %s\n", 1307 item->product_id, 1308 currency); 1309 return GNUNET_SYSERR; 1310 } 1311 if (0 > 1312 TALER_amount_add (total, 1313 total, 1314 &line_total)) 1315 { 1316 GNUNET_break (0); 1317 return GNUNET_SYSERR; 1318 } 1319 } 1320 return GNUNET_OK; 1321 } 1322 1323 1324 /** 1325 * Compute total price. 1326 * 1327 * @param[in,out] uc use context 1328 */ 1329 static void 1330 handle_phase_compute_price (struct UseContext *uc) 1331 { 1332 const char *primary_currency; 1333 enum GNUNET_GenericReturnValue ret; 1334 1335 switch (uc->template_type) 1336 { 1337 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1338 uc->compute_price.totals 1339 = GNUNET_new (struct TALER_Amount); 1340 uc->compute_price.totals_len 1341 = 1; 1342 if (uc->parse_request.no_amount) 1343 { 1344 GNUNET_assert (! uc->template_contract.no_amount); 1345 *uc->compute_price.totals 1346 = uc->template_contract.amount; 1347 } 1348 else 1349 { 1350 GNUNET_assert (uc->template_contract.no_amount); 1351 *uc->compute_price.totals 1352 = uc->parse_request.amount; 1353 } 1354 uc->phase++; 1355 return; 1356 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 1357 /* handled below */ 1358 break; 1359 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1360 { 1361 const struct TALER_MERCHANT_TemplateContractPaivana *tcp 1362 = &uc->template_contract.details.paivana; 1363 json_t *choices; 1364 1365 choices = json_array (); 1366 GNUNET_assert (NULL != choices); 1367 for (size_t i = 0; i < tcp->choices_len; i++) 1368 { 1369 /* Make deep copy, we're going to MODIFY it! */ 1370 struct TALER_MERCHANT_ContractChoice choice 1371 = tcp->choices[i]; 1372 1373 choice.no_tip = uc->parse_request.no_tip; 1374 if (! uc->parse_request.no_tip) 1375 { 1376 if (GNUNET_YES != 1377 TALER_amount_cmp_currency (&choice.amount, 1378 &uc->parse_request.tip)) 1379 continue; /* tip does not match choice currency */ 1380 choice.tip = uc->parse_request.tip; 1381 if (0 > 1382 TALER_amount_add (&choice.amount, 1383 &choice.amount, 1384 &uc->parse_request.tip)) 1385 { 1386 GNUNET_break (0); 1387 use_reply_with_error (uc, 1388 MHD_HTTP_INTERNAL_SERVER_ERROR, 1389 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1390 "tip"); 1391 return; 1392 } 1393 } 1394 GNUNET_assert (0 == 1395 json_array_append_new ( 1396 choices, 1397 TALER_MERCHANT_json_from_contract_choice (&choice, 1398 true))); 1399 } 1400 if (0 == json_array_size (choices)) 1401 { 1402 GNUNET_break_op (0); 1403 json_decref (choices); 1404 use_reply_with_error (uc, 1405 MHD_HTTP_CONFLICT, 1406 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY, 1407 "tip"); 1408 return; 1409 } 1410 uc->compute_price.choices = choices; 1411 } 1412 /* Note: we already did the tip and pricing 1413 fully here, so we skip these phases. */ 1414 uc->phase = USE_PHASE_CREATE_ORDER; 1415 return; 1416 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1417 GNUNET_assert (0); 1418 } 1419 primary_currency = uc->template_contract.currency; 1420 if (! uc->parse_request.no_amount) 1421 primary_currency = uc->parse_request.amount.currency; 1422 if (! uc->parse_request.no_tip) 1423 primary_currency = uc->parse_request.tip.currency; 1424 if (NULL == primary_currency) 1425 { 1426 ret = compute_totals_per_currency (uc); 1427 } 1428 else 1429 { 1430 uc->compute_price.totals 1431 = GNUNET_new (struct TALER_Amount); 1432 uc->compute_price.totals_len 1433 = 1; 1434 ret = compute_inventory_total ( 1435 uc->parse_request.inventory.items_len, 1436 uc->parse_request.inventory.items, 1437 primary_currency, 1438 uc->compute_price.totals); 1439 } 1440 if (GNUNET_SYSERR == ret) 1441 { 1442 use_reply_with_error ( 1443 uc, 1444 MHD_HTTP_INTERNAL_SERVER_ERROR, 1445 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1446 "calculation of currency totals failed"); 1447 return; 1448 } 1449 if (GNUNET_NO == ret) 1450 { 1451 use_reply_with_error (uc, 1452 MHD_HTTP_CONFLICT, 1453 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY, 1454 NULL); 1455 return; 1456 } 1457 1458 uc->phase++; 1459 } 1460 1461 1462 /* ***************** USE_PHASE_CHECK_TIP **************** */ 1463 1464 1465 /** 1466 * Check that tip specified is reasonable and add to total. 1467 * 1468 * @param[in,out] uc use context 1469 */ 1470 static void 1471 handle_phase_check_tip (struct UseContext *uc) 1472 { 1473 struct TALER_Amount *total_amount; 1474 1475 if (uc->parse_request.no_tip) 1476 { 1477 uc->phase++; 1478 return; 1479 } 1480 if (0 == uc->compute_price.totals_len) 1481 { 1482 if (! TMH_test_exchange_configured_for_currency ( 1483 uc->parse_request.tip.currency)) 1484 { 1485 GNUNET_break_op (0); 1486 use_reply_with_error (uc, 1487 MHD_HTTP_CONFLICT, 1488 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1489 "Tip currency is not supported by backend"); 1490 return; 1491 } 1492 uc->compute_price.totals 1493 = GNUNET_new (struct TALER_Amount); 1494 uc->compute_price.totals_len 1495 = 1; 1496 *uc->compute_price.totals 1497 = uc->parse_request.tip; 1498 uc->phase++; 1499 return; 1500 } 1501 GNUNET_assert (1 == uc->compute_price.totals_len); 1502 total_amount = &uc->compute_price.totals[0]; 1503 if (GNUNET_YES != 1504 TALER_amount_cmp_currency (&uc->parse_request.tip, 1505 total_amount)) 1506 { 1507 GNUNET_break_op (0); 1508 use_reply_with_error (uc, 1509 MHD_HTTP_CONFLICT, 1510 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1511 uc->parse_request.tip.currency); 1512 return; 1513 } 1514 if (0 > 1515 TALER_amount_add (total_amount, 1516 total_amount, 1517 &uc->parse_request.tip)) 1518 { 1519 GNUNET_break (0); 1520 use_reply_with_error (uc, 1521 MHD_HTTP_INTERNAL_SERVER_ERROR, 1522 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1523 "tip"); 1524 return; 1525 } 1526 uc->phase++; 1527 } 1528 1529 1530 /* ***************** USE_PHASE_CHECK_TOTAL **************** */ 1531 1532 /** 1533 * Check that if the client specified a total, 1534 * it matches our own calculation. 1535 * 1536 * @param[in,out] uc use context 1537 */ 1538 static void 1539 handle_phase_check_total (struct UseContext *uc) 1540 { 1541 GNUNET_assert (1 <= uc->compute_price.totals_len); 1542 if (! uc->parse_request.no_amount) 1543 { 1544 GNUNET_assert (1 == uc->compute_price.totals_len); 1545 GNUNET_assert (GNUNET_YES == 1546 TALER_amount_cmp_currency (&uc->parse_request.amount, 1547 &uc->compute_price.totals[0])); 1548 if (0 != 1549 TALER_amount_cmp (&uc->parse_request.amount, 1550 &uc->compute_price.totals[0])) 1551 { 1552 GNUNET_break_op (0); 1553 use_reply_with_error (uc, 1554 MHD_HTTP_CONFLICT, 1555 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 1556 TALER_amount2s (&uc->compute_price.totals[0])); 1557 return; 1558 } 1559 } 1560 uc->phase++; 1561 } 1562 1563 1564 /* ***************** USE_PHASE_CREATE_ORDER **************** */ 1565 1566 1567 /** 1568 * Create order request for inventory templates. 1569 * 1570 * @param[in,out] uc use context 1571 */ 1572 static void 1573 create_using_templates_inventory (struct UseContext *uc) 1574 { 1575 json_t *inventory_products; 1576 json_t *choices; 1577 1578 inventory_products = json_array (); 1579 GNUNET_assert (NULL != inventory_products); 1580 for (unsigned int i = 0; 1581 i < uc->parse_request.inventory.items_len; 1582 i++) 1583 { 1584 const struct InventoryTemplateItemContext *item = 1585 &uc->parse_request.inventory.items[i]; 1586 1587 GNUNET_assert (0 == 1588 json_array_append_new ( 1589 inventory_products, 1590 GNUNET_JSON_PACK ( 1591 GNUNET_JSON_pack_string ("product_id", 1592 item->product_id), 1593 GNUNET_JSON_pack_string ("unit_quantity", 1594 item->unit_quantity)))); 1595 } 1596 choices = json_array (); 1597 GNUNET_assert (NULL != choices); 1598 for (unsigned int i = 0; 1599 i < uc->compute_price.totals_len; 1600 i++) 1601 { 1602 GNUNET_assert (0 == 1603 json_array_append_new ( 1604 choices, 1605 GNUNET_JSON_PACK ( 1606 TALER_JSON_pack_amount ("amount", 1607 &uc->compute_price.totals[i]), 1608 GNUNET_JSON_pack_allow_null ( 1609 TALER_JSON_pack_amount ("tip", 1610 uc->parse_request.no_tip 1611 ? NULL 1612 : &uc->parse_request.tip)) 1613 ))); 1614 } 1615 1616 uc->ihc.request_body 1617 = GNUNET_JSON_PACK ( 1618 GNUNET_JSON_pack_allow_null ( 1619 GNUNET_JSON_pack_string ("otp_id", 1620 uc->lookup_template.etp.otp_id)), 1621 GNUNET_JSON_pack_array_steal ("inventory_products", 1622 inventory_products), 1623 GNUNET_JSON_pack_object_steal ( 1624 "order", 1625 GNUNET_JSON_PACK ( 1626 GNUNET_JSON_pack_uint64 ("version", 1627 1), 1628 GNUNET_JSON_pack_array_steal ("choices", 1629 choices), 1630 GNUNET_JSON_pack_string ("summary", 1631 NULL == uc->parse_request.summary 1632 ? uc->template_contract.summary 1633 : uc->parse_request.summary)))); 1634 } 1635 1636 1637 /** 1638 * Create order request for fixed-order templates. 1639 * 1640 * @param[in,out] uc use context 1641 */ 1642 static void 1643 create_using_templates_fixed (struct UseContext *uc) 1644 { 1645 uc->ihc.request_body 1646 = GNUNET_JSON_PACK ( 1647 GNUNET_JSON_pack_allow_null ( 1648 GNUNET_JSON_pack_string ("otp_id", 1649 uc->lookup_template.etp.otp_id)), 1650 GNUNET_JSON_pack_object_steal ( 1651 "order", 1652 GNUNET_JSON_PACK ( 1653 TALER_JSON_pack_amount ( 1654 "amount", 1655 &uc->compute_price.totals[0]), 1656 GNUNET_JSON_pack_allow_null ( 1657 TALER_JSON_pack_amount ("tip", 1658 uc->parse_request.no_tip 1659 ? NULL 1660 : &uc->parse_request.tip)), 1661 GNUNET_JSON_pack_string ( 1662 "summary", 1663 NULL == uc->parse_request.summary 1664 ? uc->template_contract.summary 1665 : uc->parse_request.summary)))); 1666 } 1667 1668 1669 /** 1670 * Create order request for paivana templates. 1671 * 1672 * @param[in,out] uc use context 1673 */ 1674 static void 1675 create_using_templates_paivana (struct UseContext *uc) 1676 { 1677 uc->ihc.request_body 1678 = GNUNET_JSON_PACK ( 1679 GNUNET_JSON_pack_string ( 1680 "session_id", 1681 uc->parse_request.paivana.paivana_id), 1682 GNUNET_JSON_pack_object_steal ( 1683 "order", 1684 GNUNET_JSON_PACK ( 1685 GNUNET_JSON_pack_uint64 ("version", 1686 1), 1687 GNUNET_JSON_pack_array_incref ("choices", 1688 uc->compute_price.choices), 1689 GNUNET_JSON_pack_string ( 1690 "summary", 1691 NULL == uc->parse_request.summary 1692 ? uc->template_contract.summary 1693 : uc->parse_request.summary), 1694 GNUNET_JSON_pack_string ("fulfillment_url", 1695 uc->parse_request.paivana.website)))); 1696 } 1697 1698 1699 static void 1700 handle_phase_create_order (struct UseContext *uc) 1701 { 1702 GNUNET_assert (NULL == uc->ihc.request_body); 1703 switch (uc->template_type) 1704 { 1705 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1706 create_using_templates_fixed (uc); 1707 break; 1708 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1709 create_using_templates_paivana (uc); 1710 break; 1711 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 1712 create_using_templates_inventory (uc); 1713 break; 1714 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1715 GNUNET_assert (0); 1716 } 1717 uc->phase++; 1718 } 1719 1720 1721 /* ***************** Main handler **************** */ 1722 1723 enum MHD_Result 1724 TMH_post_using_templates_ID ( 1725 const struct TMH_RequestHandler *rh, 1726 struct MHD_Connection *connection, 1727 struct TMH_HandlerContext *hc) 1728 { 1729 struct UseContext *uc = hc->ctx; 1730 1731 (void) rh; 1732 if (NULL == uc) 1733 { 1734 uc = GNUNET_new (struct UseContext); 1735 uc->hc = hc; 1736 hc->ctx = uc; 1737 hc->cc = &cleanup_use_context; 1738 uc->ihc.instance = hc->instance; 1739 uc->phase = USE_PHASE_PARSE_REQUEST; 1740 uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID; 1741 } 1742 1743 while (1) 1744 { 1745 switch (uc->phase) 1746 { 1747 case USE_PHASE_PARSE_REQUEST: 1748 handle_phase_parse_request (uc); 1749 break; 1750 case USE_PHASE_LOOKUP_TEMPLATE: 1751 handle_phase_lookup_template (uc); 1752 break; 1753 case USE_PHASE_PARSE_TEMPLATE: 1754 handle_phase_template_contract (uc); 1755 break; 1756 case USE_PHASE_DB_FETCH: 1757 handle_phase_db_fetch (uc); 1758 break; 1759 case USE_PHASE_VERIFY: 1760 handle_phase_verify (uc); 1761 break; 1762 case USE_PHASE_COMPUTE_PRICE: 1763 handle_phase_compute_price (uc); 1764 break; 1765 case USE_PHASE_CHECK_TIP: 1766 handle_phase_check_tip (uc); 1767 break; 1768 case USE_PHASE_CHECK_TOTAL: 1769 handle_phase_check_total (uc); 1770 break; 1771 case USE_PHASE_CREATE_ORDER: 1772 handle_phase_create_order (uc); 1773 break; 1774 case USE_PHASE_SUBMIT_ORDER: 1775 return TMH_private_post_orders ( 1776 NULL, /* not even used */ 1777 connection, 1778 &uc->ihc); 1779 case USE_PHASE_FINISHED_MHD_YES: 1780 return MHD_YES; 1781 case USE_PHASE_FINISHED_MHD_NO: 1782 return MHD_NO; 1783 } 1784 } 1785 }