taler-merchant-httpd_post-templates-TEMPLATE_ID.c (50457B)
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 ( 294 unsigned int items_len, 295 struct InventoryTemplateItemContext items[static 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 TALER_MERCHANT_template_contract_free (&uc->template_contract); 324 GNUNET_free (uc->compute_price.totals); 325 uc->compute_price.totals_len = 0; 326 json_decref (uc->compute_price.choices); 327 if (NULL != uc->ihc.cc) 328 uc->ihc.cc (uc->ihc.ctx); 329 GNUNET_free (uc->ihc.infix); 330 json_decref (uc->ihc.request_body); 331 GNUNET_free (uc); 332 } 333 334 335 /** 336 * Finalize a template use request. 337 * 338 * @param[in,out] uc use context 339 * @param ret handler return value 340 */ 341 static void 342 use_finalize (struct UseContext *uc, 343 enum MHD_Result ret) 344 { 345 uc->phase = (MHD_YES == ret) 346 ? USE_PHASE_FINISHED_MHD_YES 347 : USE_PHASE_FINISHED_MHD_NO; 348 } 349 350 351 /** 352 * Finalize after JSON parsing result. 353 * 354 * @param[in,out] uc use context 355 * @param res parse result 356 */ 357 static void 358 use_finalize_parse (struct UseContext *uc, 359 enum GNUNET_GenericReturnValue res) 360 { 361 GNUNET_assert (GNUNET_OK != res); 362 use_finalize (uc, 363 (GNUNET_NO == res) 364 ? MHD_YES 365 : MHD_NO); 366 } 367 368 369 /** 370 * Reply with error and finalize the request. 371 * 372 * @param[in,out] uc use context 373 * @param http_status HTTP status code 374 * @param ec error code 375 * @param detail error detail 376 */ 377 static void 378 use_reply_with_error (struct UseContext *uc, 379 unsigned int http_status, 380 enum TALER_ErrorCode ec, 381 const char *detail) 382 { 383 enum MHD_Result mret; 384 385 mret = TALER_MHD_reply_with_error (uc->hc->connection, 386 http_status, 387 ec, 388 detail); 389 use_finalize (uc, 390 mret); 391 } 392 393 394 /* ***************** USE_PHASE_PARSE_REQUEST **************** */ 395 396 /** 397 * Parse request data for inventory templates. 398 * 399 * @param[in,out] uc use context 400 * @return #GNUNET_OK on success 401 */ 402 static enum GNUNET_GenericReturnValue 403 parse_using_templates_inventory_request ( 404 struct UseContext *uc) 405 { 406 const json_t *inventory_selection; 407 struct GNUNET_JSON_Specification spec[] = { 408 GNUNET_JSON_spec_array_const ("inventory_selection", 409 &inventory_selection), 410 GNUNET_JSON_spec_end () 411 }; 412 enum GNUNET_GenericReturnValue res; 413 414 GNUNET_assert (NULL == uc->ihc.request_body); 415 res = TALER_MHD_parse_json_data (uc->hc->connection, 416 uc->hc->request_body, 417 spec); 418 if (GNUNET_OK != res) 419 { 420 GNUNET_break_op (0); 421 use_finalize_parse (uc, 422 res); 423 return GNUNET_SYSERR; 424 } 425 426 if ( (! uc->parse_request.no_amount) && 427 (! TMH_test_exchange_configured_for_currency ( 428 uc->parse_request.amount.currency)) ) 429 { 430 GNUNET_break_op (0); 431 use_reply_with_error (uc, 432 MHD_HTTP_CONFLICT, 433 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 434 "Currency is not supported by backend"); 435 return GNUNET_SYSERR; 436 } 437 438 for (size_t i = 0; i < json_array_size (inventory_selection); i++) 439 { 440 struct InventoryTemplateItemContext item = { 0 }; 441 struct GNUNET_JSON_Specification ispec[] = { 442 GNUNET_JSON_spec_string ("product_id", 443 &item.product_id), 444 GNUNET_JSON_spec_string ("quantity", 445 &item.unit_quantity), 446 GNUNET_JSON_spec_end () 447 }; 448 const char *err_name; 449 unsigned int err_line; 450 451 res = GNUNET_JSON_parse (json_array_get (inventory_selection, 452 i), 453 ispec, 454 &err_name, 455 &err_line); 456 if (GNUNET_OK != res) 457 { 458 GNUNET_break_op (0); 459 use_reply_with_error (uc, 460 MHD_HTTP_BAD_REQUEST, 461 TALER_EC_GENERIC_PARAMETER_MALFORMED, 462 "inventory_selection"); 463 return GNUNET_SYSERR; 464 } 465 466 GNUNET_array_append (uc->parse_request.inventory.items, 467 uc->parse_request.inventory.items_len, 468 item); 469 } 470 return GNUNET_OK; 471 } 472 473 474 /** 475 * Parse request data for paivana templates. 476 * 477 * @param[in,out] uc use context 478 * @return #GNUNET_OK on success 479 */ 480 static enum GNUNET_GenericReturnValue 481 parse_using_templates_paivana_request ( 482 struct UseContext *uc) 483 { 484 struct GNUNET_JSON_Specification spec[] = { 485 GNUNET_JSON_spec_string ("website", 486 &uc->parse_request.paivana.website), 487 GNUNET_JSON_spec_string ("paivana_id", 488 &uc->parse_request.paivana.paivana_id), 489 GNUNET_JSON_spec_end () 490 }; 491 enum GNUNET_GenericReturnValue res; 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 if (NULL == dash) 521 { 522 GNUNET_break_op (0); 523 use_reply_with_error (uc, 524 MHD_HTTP_BAD_REQUEST, 525 TALER_EC_GENERIC_PARAMETER_MALFORMED, 526 "paivana_id"); 527 return GNUNET_SYSERR; 528 } 529 { 530 size_t olen; 531 void *out = NULL; 532 533 olen = GNUNET_STRINGS_base64url_decode (dash + 1, 534 strlen (dash + 1), 535 &out); 536 GNUNET_free (out); 537 if (sizeof (struct GNUNET_ShortHashCode) != olen) 538 { 539 GNUNET_break_op (0); 540 use_reply_with_error (uc, 541 MHD_HTTP_BAD_REQUEST, 542 TALER_EC_GENERIC_PARAMETER_MALFORMED, 543 "paivana_id"); 544 return GNUNET_SYSERR; 545 } 546 } 547 return GNUNET_OK; 548 } 549 550 551 /** 552 * Main function for the #USE_PHASE_PARSE_REQUEST. 553 * 554 * @param[in,out] uc context to update 555 */ 556 static void 557 handle_phase_parse_request ( 558 struct UseContext *uc) 559 { 560 const char *template_type = NULL; 561 struct GNUNET_JSON_Specification spec[] = { 562 GNUNET_JSON_spec_mark_optional ( 563 GNUNET_JSON_spec_string ("template_type", 564 &template_type), 565 NULL), 566 GNUNET_JSON_spec_mark_optional ( 567 TALER_JSON_spec_amount_any ("tip", 568 &uc->parse_request.tip), 569 &uc->parse_request.no_tip), 570 GNUNET_JSON_spec_mark_optional ( 571 GNUNET_JSON_spec_string ("summary", 572 &uc->parse_request.summary), 573 NULL), 574 GNUNET_JSON_spec_mark_optional ( 575 TALER_JSON_spec_amount_any ("amount", 576 &uc->parse_request.amount), 577 &uc->parse_request.no_amount), 578 GNUNET_JSON_spec_end () 579 }; 580 enum GNUNET_GenericReturnValue res; 581 582 res = TALER_MHD_parse_json_data (uc->hc->connection, 583 uc->hc->request_body, 584 spec); 585 if (GNUNET_OK != res) 586 { 587 GNUNET_break_op (0); 588 use_finalize_parse (uc, 589 res); 590 return; 591 } 592 if (NULL == template_type) 593 template_type = "fixed-order"; 594 uc->template_type 595 = TALER_MERCHANT_template_type_from_string ( 596 template_type); 597 switch (uc->template_type) 598 { 599 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 600 /* nothig left to do */ 601 uc->phase++; 602 return; 603 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 604 res = parse_using_templates_paivana_request (uc); 605 if (GNUNET_OK == res) 606 uc->phase++; 607 return; 608 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 609 res = parse_using_templates_inventory_request (uc); 610 if (GNUNET_OK == res) 611 uc->phase++; 612 return; 613 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 614 break; 615 } 616 GNUNET_break (0); 617 use_reply_with_error ( 618 uc, 619 MHD_HTTP_BAD_REQUEST, 620 TALER_EC_GENERIC_PARAMETER_MALFORMED, 621 "template_type"); 622 } 623 624 625 /* ***************** USE_PHASE_LOOKUP_TEMPLATE **************** */ 626 627 /** 628 * Main function for the #USE_PHASE_LOOKUP_TEMPLATE. 629 * 630 * @param[in,out] uc context to update 631 */ 632 static void 633 handle_phase_lookup_template ( 634 struct UseContext *uc) 635 { 636 struct TMH_MerchantInstance *mi = uc->hc->instance; 637 const char *template_id = uc->hc->infix; 638 enum GNUNET_DB_QueryStatus qs; 639 640 qs = TALER_MERCHANTDB_lookup_template (TMH_db, 641 mi->settings.id, 642 template_id, 643 &uc->lookup_template.etp); 644 switch (qs) 645 { 646 case GNUNET_DB_STATUS_HARD_ERROR: 647 /* Clean up and fail hard */ 648 GNUNET_break (0); 649 use_reply_with_error (uc, 650 MHD_HTTP_INTERNAL_SERVER_ERROR, 651 TALER_EC_GENERIC_DB_FETCH_FAILED, 652 "lookup_template"); 653 return; 654 case GNUNET_DB_STATUS_SOFT_ERROR: 655 /* this should be impossible (single select) */ 656 GNUNET_break (0); 657 use_reply_with_error (uc, 658 MHD_HTTP_INTERNAL_SERVER_ERROR, 659 TALER_EC_GENERIC_DB_FETCH_FAILED, 660 "lookup_template"); 661 return; 662 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 663 /* template not found! */ 664 use_reply_with_error (uc, 665 MHD_HTTP_NOT_FOUND, 666 TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN, 667 template_id); 668 return; 669 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 670 /* all good */ 671 break; 672 } 673 if (uc->template_type != 674 TALER_MERCHANT_template_type_from_contract ( 675 uc->lookup_template.etp.template_contract)) 676 { 677 GNUNET_break_op (0); 678 use_reply_with_error ( 679 uc, 680 MHD_HTTP_CONFLICT, 681 TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_TYPE, 682 "template_contract has different type"); 683 return; 684 } 685 uc->phase++; 686 } 687 688 689 /* ***************** USE_PHASE_PARSE_TEMPLATE **************** */ 690 691 692 /** 693 * Parse template. 694 * 695 * @param[in,out] uc use context 696 */ 697 static void 698 handle_phase_template_contract (struct UseContext *uc) 699 { 700 const char *err_name; 701 enum GNUNET_GenericReturnValue res; 702 703 res = TALER_MERCHANT_template_contract_parse ( 704 uc->lookup_template.etp.template_contract, 705 &uc->template_contract, 706 &err_name); 707 if (GNUNET_OK != res) 708 { 709 GNUNET_break (0); 710 use_reply_with_error (uc, 711 MHD_HTTP_INTERNAL_SERVER_ERROR, 712 TALER_EC_GENERIC_DB_FETCH_FAILED, 713 err_name); 714 return; 715 } 716 uc->phase++; 717 } 718 719 720 /* ***************** USE_PHASE_DB_FETCH **************** */ 721 722 /** 723 * Fetch DB data for inventory templates. 724 * 725 * @param[in,out] uc use context 726 */ 727 static void 728 handle_phase_db_fetch (struct UseContext *uc) 729 { 730 struct TMH_MerchantInstance *mi = uc->hc->instance; 731 732 switch (uc->template_type) 733 { 734 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 735 uc->phase++; 736 return; 737 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 738 uc->phase++; 739 return; 740 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 741 break; 742 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 743 GNUNET_assert (0); 744 } 745 746 for (unsigned int i = 0; 747 i < uc->parse_request.inventory.items_len; 748 i++) 749 { 750 struct InventoryTemplateItemContext *item = 751 &uc->parse_request.inventory.items[i]; 752 enum GNUNET_DB_QueryStatus qs; 753 754 qs = TALER_MERCHANTDB_lookup_product (TMH_db, 755 mi->settings.id, 756 item->product_id, 757 &item->pd, 758 &item->num_categories, 759 &item->categories); 760 switch (qs) 761 { 762 case GNUNET_DB_STATUS_HARD_ERROR: 763 GNUNET_break (0); 764 use_reply_with_error (uc, 765 MHD_HTTP_INTERNAL_SERVER_ERROR, 766 TALER_EC_GENERIC_DB_FETCH_FAILED, 767 "lookup_product"); 768 return; 769 case GNUNET_DB_STATUS_SOFT_ERROR: 770 GNUNET_break (0); 771 use_reply_with_error (uc, 772 MHD_HTTP_INTERNAL_SERVER_ERROR, 773 TALER_EC_GENERIC_DB_FETCH_FAILED, 774 "lookup_product"); 775 return; 776 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 777 use_reply_with_error (uc, 778 MHD_HTTP_NOT_FOUND, 779 TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN, 780 item->product_id); 781 return; 782 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 783 break; 784 } 785 } 786 uc->phase++; 787 } 788 789 790 /* *************** Helpers for USE_PHASE_VERIFY ***************** */ 791 792 /** 793 * Check if the given product ID appears in the array of allowed_products. 794 * 795 * @param allowed_products JSON array of product IDs allowed by the template, may be NULL 796 * @param product_id product ID to check 797 * @return true if the product ID is in the list 798 */ 799 static bool 800 product_id_allowed (const json_t *allowed_products, 801 const char *product_id) 802 { 803 const json_t *entry; 804 size_t idx; 805 806 if (NULL == allowed_products) 807 return false; 808 json_array_foreach ((json_t *) allowed_products, idx, entry) 809 { 810 if (! json_is_string (entry)) 811 { 812 GNUNET_break (0); 813 continue; 814 } 815 if (0 == strcmp (json_string_value (entry), 816 product_id)) 817 return true; 818 } 819 return false; 820 } 821 822 823 /** 824 * Check if any product category is in the selected_categories list. 825 * 826 * @param allowed_categories JSON array of categories allowed by the template, may be NULL 827 * @param num_categories length of @a categories 828 * @param categories list of categories of the selected product 829 * @return true if any category of the product is in the list of allowed categories matches 830 */ 831 static bool 832 category_allowed (const json_t *allowed_categories, 833 size_t num_categories, 834 const uint64_t categories[num_categories]) 835 { 836 const json_t *entry; 837 size_t idx; 838 839 if (NULL == allowed_categories) 840 return false; 841 json_array_foreach ((json_t *) allowed_categories, 842 idx, 843 entry) 844 { 845 uint64_t selected_id; 846 847 if (! json_is_integer (entry)) 848 { 849 GNUNET_break (0); 850 continue; 851 } 852 if (0 > json_integer_value (entry)) 853 { 854 GNUNET_break (0); 855 continue; 856 } 857 selected_id = (uint64_t) json_integer_value (entry); 858 for (size_t i = 0; i < num_categories; i++) 859 { 860 if (categories[i] == selected_id) 861 return true; 862 } 863 } 864 return false; 865 } 866 867 868 /** 869 * Verify request data for inventory templates. 870 * Checks that the selected products are allowed 871 * for this template. 872 * 873 * @param[in,out] uc use context 874 * @return #GNUNET_OK on success 875 */ 876 static enum GNUNET_GenericReturnValue 877 verify_using_templates_inventory (struct UseContext *uc) 878 { 879 if (uc->template_contract.details.inventory.choose_one && 880 (1 != uc->parse_request.inventory.items_len)) 881 { 882 GNUNET_break_op (0); 883 use_reply_with_error (uc, 884 MHD_HTTP_CONFLICT, 885 TALER_EC_GENERIC_PARAMETER_MALFORMED, 886 "inventory_selection"); 887 return GNUNET_SYSERR; 888 } 889 if (uc->template_contract.details.inventory.selected_all) 890 return GNUNET_OK; 891 for (unsigned int i = 0; 892 i < uc->parse_request.inventory.items_len; 893 i++) 894 { 895 struct InventoryTemplateItemContext *item = 896 &uc->parse_request.inventory.items[i]; 897 const char *eparam = NULL; 898 899 if (GNUNET_OK != 900 TALER_MERCHANT_vk_process_quantity_inputs ( 901 TALER_MERCHANT_VK_QUANTITY, 902 item->pd.allow_fractional_quantity, 903 true, 904 0, 905 false, 906 item->unit_quantity, 907 &item->quantity_value, 908 &item->quantity_frac, 909 &eparam)) 910 { 911 GNUNET_break_op (0); 912 use_reply_with_error (uc, 913 MHD_HTTP_BAD_REQUEST, 914 TALER_EC_GENERIC_PARAMETER_MALFORMED, 915 eparam); 916 return GNUNET_SYSERR; 917 } 918 919 if (0 == item->pd.price_array_length) 920 { 921 GNUNET_break (0); 922 use_reply_with_error (uc, 923 MHD_HTTP_INTERNAL_SERVER_ERROR, 924 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 925 "price_array"); 926 return GNUNET_SYSERR; 927 } 928 929 /* The line-total computation multiplies the unit price by the 930 integer quantity using TALER_amount_multiply(), whose factor 931 argument is only a uint32_t. A quantity that does not fit into 932 32 bits would be silently truncated by the (uint32_t) cast in 933 compute_line_total() (e.g. quantity == 2^32 truncates to 0), 934 producing a wrong -- and attacker-controllable, much too low -- 935 price. Reject such quantities rather than truncate them. */ 936 if (item->quantity_value > UINT32_MAX) 937 { 938 GNUNET_break_op (0); 939 use_reply_with_error (uc, 940 MHD_HTTP_BAD_REQUEST, 941 TALER_EC_GENERIC_PARAMETER_MALFORMED, 942 "unit_quantity"); 943 return GNUNET_SYSERR; 944 } 945 } 946 947 for (unsigned int i = 0; 948 i < uc->parse_request.inventory.items_len; 949 i++) 950 { 951 struct InventoryTemplateItemContext *item = 952 &uc->parse_request.inventory.items[i]; 953 954 if (product_id_allowed (uc->template_contract.details.inventory. 955 selected_products, 956 item->product_id)) 957 continue; 958 if (category_allowed ( 959 uc->template_contract.details.inventory.selected_categories, 960 item->num_categories, 961 item->categories)) 962 continue; 963 GNUNET_break_op (0); 964 use_reply_with_error ( 965 uc, 966 MHD_HTTP_CONFLICT, 967 TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_PRODUCT, 968 item->product_id); 969 return GNUNET_SYSERR; 970 } 971 return GNUNET_OK; 972 } 973 974 975 /** 976 * Verify request data for fixed-order templates. 977 * As here we cannot compute the total amount, either 978 * the template or the client request must provide it. 979 * 980 * @param[in,out] uc use context 981 * @return #GNUNET_OK on success 982 */ 983 static enum GNUNET_GenericReturnValue 984 verify_using_templates_fixed ( 985 struct UseContext *uc) 986 { 987 if ( (! uc->parse_request.no_amount) && 988 (! uc->template_contract.no_amount) ) 989 { 990 GNUNET_break_op (0); 991 use_reply_with_error (uc, 992 MHD_HTTP_CONFLICT, 993 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 994 NULL); 995 return GNUNET_SYSERR; 996 } 997 if (uc->parse_request.no_amount && 998 uc->template_contract.no_amount) 999 { 1000 GNUNET_break_op (0); 1001 use_reply_with_error (uc, 1002 MHD_HTTP_CONFLICT, 1003 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT, 1004 NULL); 1005 return GNUNET_SYSERR; 1006 } 1007 return GNUNET_OK; 1008 } 1009 1010 1011 /** 1012 * Verify request data for paivana templates. 1013 * 1014 * @param[in,out] uc use context 1015 * @return #GNUNET_OK on success 1016 */ 1017 static enum GNUNET_GenericReturnValue 1018 verify_using_templates_paivana ( 1019 struct UseContext *uc) 1020 { 1021 if (NULL != uc->template_contract.details.paivana.website_regex) 1022 { 1023 regex_t ex; 1024 bool allowed = false; 1025 1026 if (0 != regcomp (&ex, 1027 uc->template_contract.details.paivana.website_regex, 1028 REG_NOSUB | REG_EXTENDED)) 1029 { 1030 GNUNET_break_op (0); 1031 return GNUNET_SYSERR; 1032 } 1033 if (0 == 1034 regexec (&ex, 1035 uc->parse_request.paivana.website, 1036 0, NULL, 1037 0)) 1038 { 1039 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1040 "Website `%s' allowed by template\n", 1041 uc->parse_request.paivana.website); 1042 allowed = true; 1043 } 1044 regfree (&ex); 1045 if (! allowed) 1046 { 1047 GNUNET_break_op (0); 1048 use_reply_with_error (uc, 1049 MHD_HTTP_BAD_REQUEST, 1050 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1051 "website_regex"); 1052 return GNUNET_SYSERR; 1053 } 1054 } 1055 return GNUNET_OK; 1056 } 1057 1058 1059 /** 1060 * Verify that the client request is structurally acceptable for the specified 1061 * template. Does NOT check the total amount being reasonable. 1062 * 1063 * @param[in,out] uc use context 1064 */ 1065 static void 1066 handle_phase_verify ( 1067 struct UseContext *uc) 1068 { 1069 enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; 1070 1071 if ( (NULL != uc->parse_request.summary) && 1072 (NULL != uc->template_contract.summary) ) 1073 { 1074 GNUNET_break_op (0); 1075 use_reply_with_error (uc, 1076 MHD_HTTP_CONFLICT, 1077 TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT, 1078 NULL); 1079 return; 1080 } 1081 if ( (NULL == uc->parse_request.summary) && 1082 (NULL == uc->template_contract.summary) ) 1083 { 1084 GNUNET_break_op (0); 1085 use_reply_with_error (uc, 1086 MHD_HTTP_CONFLICT, 1087 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY, 1088 NULL); 1089 return; 1090 } 1091 if ( (! uc->parse_request.no_amount) && 1092 (NULL != uc->template_contract.currency) && 1093 (0 != strcasecmp (uc->template_contract.currency, 1094 uc->parse_request.amount.currency)) ) 1095 { 1096 GNUNET_break_op (0); 1097 use_reply_with_error (uc, 1098 MHD_HTTP_CONFLICT, 1099 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1100 uc->template_contract.currency); 1101 return; 1102 } 1103 switch (uc->template_type) 1104 { 1105 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1106 res = verify_using_templates_fixed (uc); 1107 break; 1108 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1109 res = verify_using_templates_paivana (uc); 1110 break; 1111 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 1112 res = verify_using_templates_inventory (uc); 1113 break; 1114 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1115 GNUNET_assert (0); 1116 } 1117 if (GNUNET_OK == res) 1118 uc->phase++; 1119 } 1120 1121 1122 /* ***************** USE_PHASE_COMPUTE_PRICE **************** */ 1123 1124 1125 /** 1126 * Compute the line total for a product based on quantity. 1127 * 1128 * @param unit_price price per unit 1129 * @param quantity integer quantity 1130 * @param quantity_frac fractional quantity (0..TALER_MERCHANT_UNIT_FRAC_BASE-1) 1131 * @param[out] line_total resulting line total 1132 * @return #GNUNET_OK on success 1133 */ 1134 static enum GNUNET_GenericReturnValue 1135 compute_line_total (const struct TALER_Amount *unit_price, 1136 uint64_t quantity, 1137 uint32_t quantity_frac, 1138 struct TALER_Amount *line_total) 1139 { 1140 struct TALER_Amount tmp; 1141 1142 GNUNET_assert (GNUNET_OK == 1143 TALER_amount_set_zero (unit_price->currency, 1144 line_total)); 1145 if ( (0 != quantity) && 1146 (0 > 1147 TALER_amount_multiply (line_total, 1148 unit_price, 1149 (uint32_t) quantity)) ) 1150 { 1151 GNUNET_break (0); 1152 return GNUNET_SYSERR; 1153 } 1154 if (0 == quantity_frac) 1155 return GNUNET_OK; 1156 if (0 > 1157 TALER_amount_multiply (&tmp, 1158 unit_price, 1159 quantity_frac)) 1160 { 1161 GNUNET_break (0); 1162 return GNUNET_SYSERR; 1163 } 1164 TALER_amount_divide (&tmp, 1165 &tmp, 1166 TALER_MERCHANT_UNIT_FRAC_BASE); 1167 if (0 > 1168 TALER_amount_add (line_total, 1169 line_total, 1170 &tmp)) 1171 { 1172 GNUNET_break (0); 1173 return GNUNET_SYSERR; 1174 } 1175 return GNUNET_OK; 1176 } 1177 1178 1179 /** 1180 * Find the price of the given @a item in the specified 1181 * @a currency. 1182 * 1183 * @param currency currency to search price in 1184 * @param item item to check prices of 1185 * @return NULL if a suitable price was not found 1186 */ 1187 static const struct TALER_Amount * 1188 find_item_price_in_currency ( 1189 const char *currency, 1190 const struct InventoryTemplateItemContext *item) 1191 { 1192 for (size_t j = 0; j < item->pd.price_array_length; j++) 1193 { 1194 if (0 == strcasecmp (item->pd.price_array[j].currency, 1195 currency)) 1196 return &item->pd.price_array[j]; 1197 } 1198 return NULL; 1199 } 1200 1201 1202 /** 1203 * Compute totals for all currencies shared across selected products. 1204 * 1205 * @param[in,out] uc use context 1206 * @return #GNUNET_OK on success (including no price due to no items) 1207 * #GNUNET_NO if we could not find a price in any accepted currency 1208 * for all selected products 1209 * #GNUNET_SYSERR on arithmetic issues (internal error) 1210 */ 1211 static enum GNUNET_GenericReturnValue 1212 compute_totals_per_currency (struct UseContext *uc) 1213 { 1214 const struct InventoryTemplateItemContext *items 1215 = uc->parse_request.inventory.items; 1216 unsigned int items_len = uc->parse_request.inventory.items_len; 1217 1218 if (0 == items_len) 1219 return GNUNET_NO; 1220 for (size_t i = 0; i < items[0].pd.price_array_length; i++) 1221 { 1222 const struct TALER_Amount *price 1223 = &items[0].pd.price_array[i]; 1224 struct TALER_Amount zero; 1225 1226 if (! TMH_test_exchange_configured_for_currency (price->currency)) 1227 continue; 1228 GNUNET_assert (GNUNET_OK == 1229 TALER_amount_set_zero (price->currency, 1230 &zero)); 1231 GNUNET_array_append (uc->compute_price.totals, 1232 uc->compute_price.totals_len, 1233 zero); 1234 } 1235 if (0 == uc->compute_price.totals_len) 1236 { 1237 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1238 "No currency supported by our configuration in which we have prices for first selected product!\n"); 1239 return GNUNET_NO; 1240 } 1241 /* Loop through items, ensure each currency exists and sum totals. */ 1242 for (unsigned int i = 0; i < items_len; i++) 1243 { 1244 const struct InventoryTemplateItemContext *item = &items[i]; 1245 unsigned int c = 0; 1246 1247 while (c < uc->compute_price.totals_len) 1248 { 1249 struct TALER_Amount *total = &uc->compute_price.totals[c]; 1250 const struct TALER_Amount *unit_price; 1251 struct TALER_Amount line_total; 1252 1253 unit_price = find_item_price_in_currency (total->currency, 1254 item); 1255 if (NULL == unit_price) 1256 { 1257 /* Drop the currency: we have no price in one of 1258 the selected products */ 1259 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1260 "Product `%s' has no price in %s: dropping currency\n", 1261 item->product_id, 1262 total->currency); 1263 *total = uc->compute_price.totals[--uc->compute_price.totals_len]; 1264 continue; 1265 } 1266 if (GNUNET_OK != 1267 compute_line_total (unit_price, 1268 item->quantity_value, 1269 item->quantity_frac, 1270 &line_total)) 1271 { 1272 GNUNET_break (0); 1273 return GNUNET_SYSERR; 1274 } 1275 if (0 > 1276 TALER_amount_add (total, 1277 total, 1278 &line_total)) 1279 { 1280 GNUNET_break (0); 1281 return GNUNET_SYSERR; 1282 } 1283 c++; 1284 } 1285 } 1286 if (0 == uc->compute_price.totals_len) 1287 { 1288 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1289 "No currency available in which we have prices for all selected products!\n"); 1290 GNUNET_free (uc->compute_price.totals); 1291 } 1292 return (0 == uc->compute_price.totals_len) 1293 ? GNUNET_NO 1294 : GNUNET_OK; 1295 } 1296 1297 1298 /** 1299 * Compute total for only the given @a currency. 1300 * 1301 * @param items_len length of @a items 1302 * @param items inventory items 1303 * @param currency currency to total 1304 * @param[out] total computed total 1305 * @return #GNUNET_OK on success 1306 * #GNUNET_NO if we could not find a price in any accepted currency 1307 * for all selected products 1308 * #GNUNET_SYSERR on arithmetic issues (internal error) 1309 */ 1310 static enum GNUNET_GenericReturnValue 1311 compute_inventory_total (unsigned int items_len, 1312 const struct InventoryTemplateItemContext *items, 1313 const char *currency, 1314 struct TALER_Amount *total) 1315 { 1316 GNUNET_assert (NULL != currency); 1317 GNUNET_assert (GNUNET_OK == 1318 TALER_amount_set_zero (currency, 1319 total)); 1320 for (unsigned int i = 0; i < items_len; i++) 1321 { 1322 const struct InventoryTemplateItemContext *item = &items[i]; 1323 const struct TALER_Amount *unit_price; 1324 struct TALER_Amount line_total; 1325 1326 unit_price = find_item_price_in_currency (currency, 1327 item); 1328 if (NULL == unit_price) 1329 { 1330 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1331 "compute_inventory_total: no price in %s for product `%s'\n", 1332 currency, 1333 item->product_id); 1334 return GNUNET_NO; 1335 } 1336 if (GNUNET_OK != 1337 compute_line_total (unit_price, 1338 item->quantity_value, 1339 item->quantity_frac, 1340 &line_total)) 1341 { 1342 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1343 "compute_inventory_total: line total failed for %s in %s\n", 1344 item->product_id, 1345 currency); 1346 return GNUNET_SYSERR; 1347 } 1348 if (0 > 1349 TALER_amount_add (total, 1350 total, 1351 &line_total)) 1352 { 1353 GNUNET_break (0); 1354 return GNUNET_SYSERR; 1355 } 1356 } 1357 return GNUNET_OK; 1358 } 1359 1360 1361 /** 1362 * Compute total price. 1363 * 1364 * @param[in,out] uc use context 1365 */ 1366 static void 1367 handle_phase_compute_price (struct UseContext *uc) 1368 { 1369 const char *primary_currency; 1370 enum GNUNET_GenericReturnValue ret; 1371 1372 switch (uc->template_type) 1373 { 1374 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1375 uc->compute_price.totals 1376 = GNUNET_new (struct TALER_Amount); 1377 uc->compute_price.totals_len 1378 = 1; 1379 if (uc->parse_request.no_amount) 1380 { 1381 GNUNET_assert (! uc->template_contract.no_amount); 1382 *uc->compute_price.totals 1383 = uc->template_contract.amount; 1384 } 1385 else 1386 { 1387 GNUNET_assert (uc->template_contract.no_amount); 1388 *uc->compute_price.totals 1389 = uc->parse_request.amount; 1390 } 1391 uc->phase++; 1392 return; 1393 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 1394 /* handled below */ 1395 break; 1396 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1397 { 1398 const struct TALER_MERCHANT_TemplateContractPaivana *tcp 1399 = &uc->template_contract.details.paivana; 1400 json_t *choices; 1401 1402 choices = json_array (); 1403 GNUNET_assert (NULL != choices); 1404 for (size_t i = 0; i < tcp->choices_len; i++) 1405 { 1406 /* Make deep copy, we're going to MODIFY it! */ 1407 struct TALER_MERCHANT_OrderChoice choice 1408 = tcp->choices[i]; 1409 1410 choice.no_tip = uc->parse_request.no_tip; 1411 if (! uc->parse_request.no_tip) 1412 { 1413 if (GNUNET_YES != 1414 TALER_amount_cmp_currency (&choice.amount, 1415 &uc->parse_request.tip)) 1416 continue; /* tip does not match choice currency */ 1417 choice.tip = uc->parse_request.tip; 1418 if (0 > 1419 TALER_amount_add (&choice.amount, 1420 &choice.amount, 1421 &uc->parse_request.tip)) 1422 { 1423 GNUNET_break (0); 1424 use_reply_with_error (uc, 1425 MHD_HTTP_INTERNAL_SERVER_ERROR, 1426 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1427 "tip"); 1428 return; 1429 } 1430 } 1431 GNUNET_assert (0 == 1432 json_array_append_new ( 1433 choices, 1434 TALER_MERCHANT_json_from_order_choice (&choice))); 1435 } 1436 if (0 == json_array_size (choices)) 1437 { 1438 GNUNET_break_op (0); 1439 json_decref (choices); 1440 use_reply_with_error (uc, 1441 MHD_HTTP_CONFLICT, 1442 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY, 1443 "tip"); 1444 return; 1445 } 1446 uc->compute_price.choices = choices; 1447 } 1448 /* Note: we already did the tip and pricing 1449 fully here, so we skip these phases. */ 1450 uc->phase = USE_PHASE_CREATE_ORDER; 1451 return; 1452 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1453 GNUNET_assert (0); 1454 } 1455 primary_currency = uc->template_contract.currency; 1456 if (! uc->parse_request.no_amount) 1457 primary_currency = uc->parse_request.amount.currency; 1458 if (! uc->parse_request.no_tip) 1459 primary_currency = uc->parse_request.tip.currency; 1460 if (NULL == primary_currency) 1461 { 1462 ret = compute_totals_per_currency (uc); 1463 } 1464 else 1465 { 1466 uc->compute_price.totals 1467 = GNUNET_new (struct TALER_Amount); 1468 uc->compute_price.totals_len 1469 = 1; 1470 ret = compute_inventory_total ( 1471 uc->parse_request.inventory.items_len, 1472 uc->parse_request.inventory.items, 1473 primary_currency, 1474 uc->compute_price.totals); 1475 } 1476 if (GNUNET_SYSERR == ret) 1477 { 1478 use_reply_with_error ( 1479 uc, 1480 MHD_HTTP_INTERNAL_SERVER_ERROR, 1481 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1482 "calculation of currency totals failed"); 1483 return; 1484 } 1485 if (GNUNET_NO == ret) 1486 { 1487 use_reply_with_error (uc, 1488 MHD_HTTP_CONFLICT, 1489 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY, 1490 NULL); 1491 return; 1492 } 1493 1494 uc->phase++; 1495 } 1496 1497 1498 /* ***************** USE_PHASE_CHECK_TIP **************** */ 1499 1500 1501 /** 1502 * Check that tip specified is reasonable and add to total. 1503 * 1504 * @param[in,out] uc use context 1505 */ 1506 static void 1507 handle_phase_check_tip (struct UseContext *uc) 1508 { 1509 struct TALER_Amount *total_amount; 1510 1511 if (uc->parse_request.no_tip) 1512 { 1513 uc->phase++; 1514 return; 1515 } 1516 if (0 == uc->compute_price.totals_len) 1517 { 1518 if (! TMH_test_exchange_configured_for_currency ( 1519 uc->parse_request.tip.currency)) 1520 { 1521 GNUNET_break_op (0); 1522 use_reply_with_error (uc, 1523 MHD_HTTP_CONFLICT, 1524 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1525 "Tip currency is not supported by backend"); 1526 return; 1527 } 1528 uc->compute_price.totals 1529 = GNUNET_new (struct TALER_Amount); 1530 uc->compute_price.totals_len 1531 = 1; 1532 *uc->compute_price.totals 1533 = uc->parse_request.tip; 1534 uc->phase++; 1535 return; 1536 } 1537 GNUNET_assert (1 == uc->compute_price.totals_len); 1538 total_amount = &uc->compute_price.totals[0]; 1539 if (GNUNET_YES != 1540 TALER_amount_cmp_currency (&uc->parse_request.tip, 1541 total_amount)) 1542 { 1543 GNUNET_break_op (0); 1544 use_reply_with_error (uc, 1545 MHD_HTTP_CONFLICT, 1546 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1547 uc->parse_request.tip.currency); 1548 return; 1549 } 1550 if (0 > 1551 TALER_amount_add (total_amount, 1552 total_amount, 1553 &uc->parse_request.tip)) 1554 { 1555 GNUNET_break (0); 1556 use_reply_with_error (uc, 1557 MHD_HTTP_INTERNAL_SERVER_ERROR, 1558 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1559 "tip"); 1560 return; 1561 } 1562 uc->phase++; 1563 } 1564 1565 1566 /* ***************** USE_PHASE_CHECK_TOTAL **************** */ 1567 1568 /** 1569 * Check that if the client specified a total, 1570 * it matches our own calculation. 1571 * 1572 * @param[in,out] uc use context 1573 */ 1574 static void 1575 handle_phase_check_total (struct UseContext *uc) 1576 { 1577 GNUNET_assert (1 <= uc->compute_price.totals_len); 1578 if (! uc->parse_request.no_amount) 1579 { 1580 GNUNET_assert (1 == uc->compute_price.totals_len); 1581 GNUNET_assert (GNUNET_YES == 1582 TALER_amount_cmp_currency (&uc->parse_request.amount, 1583 &uc->compute_price.totals[0])); 1584 if (0 != 1585 TALER_amount_cmp (&uc->parse_request.amount, 1586 &uc->compute_price.totals[0])) 1587 { 1588 GNUNET_break_op (0); 1589 use_reply_with_error (uc, 1590 MHD_HTTP_CONFLICT, 1591 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 1592 TALER_amount2s (&uc->compute_price.totals[0])); 1593 return; 1594 } 1595 } 1596 uc->phase++; 1597 } 1598 1599 1600 /* ***************** USE_PHASE_CREATE_ORDER **************** */ 1601 1602 1603 /** 1604 * Create order request for inventory templates. 1605 * 1606 * @param[in,out] uc use context 1607 */ 1608 static void 1609 create_using_templates_inventory (struct UseContext *uc) 1610 { 1611 json_t *inventory_products; 1612 json_t *choices; 1613 1614 inventory_products = json_array (); 1615 GNUNET_assert (NULL != inventory_products); 1616 for (unsigned int i = 0; 1617 i < uc->parse_request.inventory.items_len; 1618 i++) 1619 { 1620 const struct InventoryTemplateItemContext *item = 1621 &uc->parse_request.inventory.items[i]; 1622 1623 GNUNET_assert (0 == 1624 json_array_append_new ( 1625 inventory_products, 1626 GNUNET_JSON_PACK ( 1627 GNUNET_JSON_pack_string ("product_id", 1628 item->product_id), 1629 GNUNET_JSON_pack_string ("unit_quantity", 1630 item->unit_quantity)))); 1631 } 1632 choices = json_array (); 1633 GNUNET_assert (NULL != choices); 1634 for (unsigned int i = 0; 1635 i < uc->compute_price.totals_len; 1636 i++) 1637 { 1638 GNUNET_assert (0 == 1639 json_array_append_new ( 1640 choices, 1641 GNUNET_JSON_PACK ( 1642 TALER_JSON_pack_amount ("amount", 1643 &uc->compute_price.totals[i]), 1644 GNUNET_JSON_pack_allow_null ( 1645 TALER_JSON_pack_amount ("tip", 1646 uc->parse_request.no_tip 1647 ? NULL 1648 : &uc->parse_request.tip)) 1649 ))); 1650 } 1651 1652 uc->ihc.request_body 1653 = GNUNET_JSON_PACK ( 1654 GNUNET_JSON_pack_allow_null ( 1655 GNUNET_JSON_pack_string ("otp_id", 1656 uc->lookup_template.etp.otp_id)), 1657 GNUNET_JSON_pack_array_steal ("inventory_products", 1658 inventory_products), 1659 GNUNET_JSON_pack_object_steal ( 1660 "order", 1661 GNUNET_JSON_PACK ( 1662 GNUNET_JSON_pack_uint64 ("version", 1663 1), 1664 GNUNET_JSON_pack_array_steal ("choices", 1665 choices), 1666 GNUNET_JSON_pack_string ("summary", 1667 NULL == uc->parse_request.summary 1668 ? uc->template_contract.summary 1669 : uc->parse_request.summary)))); 1670 if (! GNUNET_TIME_relative_is_forever ( 1671 uc->template_contract.max_pickup_duration)) 1672 { 1673 GNUNET_assert ( 1674 0 == 1675 json_object_set_new ( 1676 uc->ihc.request_body, 1677 "max_pickup_time", 1678 GNUNET_JSON_from_timestamp ( 1679 GNUNET_TIME_absolute_to_timestamp ( 1680 GNUNET_TIME_relative_to_absolute ( 1681 uc->template_contract.max_pickup_duration))))); 1682 } 1683 } 1684 1685 1686 /** 1687 * Create order request for fixed-order templates. 1688 * 1689 * @param[in,out] uc use context 1690 */ 1691 static void 1692 create_using_templates_fixed (struct UseContext *uc) 1693 { 1694 uc->ihc.request_body 1695 = GNUNET_JSON_PACK ( 1696 GNUNET_JSON_pack_allow_null ( 1697 GNUNET_JSON_pack_string ("otp_id", 1698 uc->lookup_template.etp.otp_id)), 1699 GNUNET_JSON_pack_object_steal ( 1700 "order", 1701 GNUNET_JSON_PACK ( 1702 TALER_JSON_pack_amount ( 1703 "amount", 1704 &uc->compute_price.totals[0]), 1705 GNUNET_JSON_pack_allow_null ( 1706 TALER_JSON_pack_amount ("tip", 1707 uc->parse_request.no_tip 1708 ? NULL 1709 : &uc->parse_request.tip)), 1710 GNUNET_JSON_pack_string ( 1711 "summary", 1712 NULL == uc->parse_request.summary 1713 ? uc->template_contract.summary 1714 : uc->parse_request.summary)))); 1715 } 1716 1717 1718 /** 1719 * Create order request for paivana templates. 1720 * 1721 * @param[in,out] uc use context 1722 */ 1723 static void 1724 create_using_templates_paivana (struct UseContext *uc) 1725 { 1726 uc->ihc.request_body 1727 = GNUNET_JSON_PACK ( 1728 GNUNET_JSON_pack_string ( 1729 "session_id", 1730 uc->parse_request.paivana.paivana_id), 1731 GNUNET_JSON_pack_object_steal ( 1732 "order", 1733 GNUNET_JSON_PACK ( 1734 GNUNET_JSON_pack_uint64 ("version", 1735 1), 1736 GNUNET_JSON_pack_array_incref ("choices", 1737 uc->compute_price.choices), 1738 GNUNET_JSON_pack_string ( 1739 "summary", 1740 NULL == uc->parse_request.summary 1741 ? uc->template_contract.summary 1742 : uc->parse_request.summary), 1743 GNUNET_JSON_pack_string ("fulfillment_url", 1744 uc->parse_request.paivana.website)))); 1745 } 1746 1747 1748 static void 1749 handle_phase_create_order (struct UseContext *uc) 1750 { 1751 GNUNET_assert (NULL == uc->ihc.request_body); 1752 switch (uc->template_type) 1753 { 1754 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1755 create_using_templates_fixed (uc); 1756 break; 1757 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1758 create_using_templates_paivana (uc); 1759 break; 1760 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 1761 create_using_templates_inventory (uc); 1762 break; 1763 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1764 GNUNET_assert (0); 1765 } 1766 uc->phase++; 1767 } 1768 1769 1770 /* ***************** Main handler **************** */ 1771 1772 enum MHD_Result 1773 TMH_post_using_templates_ID ( 1774 const struct TMH_RequestHandler *rh, 1775 struct MHD_Connection *connection, 1776 struct TMH_HandlerContext *hc) 1777 { 1778 struct UseContext *uc = hc->ctx; 1779 1780 (void) rh; 1781 if (NULL == uc) 1782 { 1783 uc = GNUNET_new (struct UseContext); 1784 uc->hc = hc; 1785 hc->ctx = uc; 1786 hc->cc = &cleanup_use_context; 1787 uc->ihc.instance = hc->instance; 1788 uc->phase = USE_PHASE_PARSE_REQUEST; 1789 uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID; 1790 } 1791 1792 while (1) 1793 { 1794 switch (uc->phase) 1795 { 1796 case USE_PHASE_PARSE_REQUEST: 1797 handle_phase_parse_request (uc); 1798 break; 1799 case USE_PHASE_LOOKUP_TEMPLATE: 1800 handle_phase_lookup_template (uc); 1801 break; 1802 case USE_PHASE_PARSE_TEMPLATE: 1803 handle_phase_template_contract (uc); 1804 break; 1805 case USE_PHASE_DB_FETCH: 1806 handle_phase_db_fetch (uc); 1807 break; 1808 case USE_PHASE_VERIFY: 1809 handle_phase_verify (uc); 1810 break; 1811 case USE_PHASE_COMPUTE_PRICE: 1812 handle_phase_compute_price (uc); 1813 break; 1814 case USE_PHASE_CHECK_TIP: 1815 handle_phase_check_tip (uc); 1816 break; 1817 case USE_PHASE_CHECK_TOTAL: 1818 handle_phase_check_total (uc); 1819 break; 1820 case USE_PHASE_CREATE_ORDER: 1821 handle_phase_create_order (uc); 1822 break; 1823 case USE_PHASE_SUBMIT_ORDER: 1824 return TMH_private_post_orders ( 1825 NULL, /* not even used */ 1826 connection, 1827 &uc->ihc); 1828 case USE_PHASE_FINISHED_MHD_YES: 1829 return MHD_YES; 1830 case USE_PHASE_FINISHED_MHD_NO: 1831 return MHD_NO; 1832 } 1833 } 1834 }