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