taler-merchant-httpd_post-templates-TEMPLATE_ID.c (49670B)
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 930 for (unsigned int i = 0; 931 i < uc->parse_request.inventory.items_len; 932 i++) 933 { 934 struct InventoryTemplateItemContext *item = 935 &uc->parse_request.inventory.items[i]; 936 937 if (product_id_allowed (uc->template_contract.details.inventory. 938 selected_products, 939 item->product_id)) 940 continue; 941 if (category_allowed ( 942 uc->template_contract.details.inventory.selected_categories, 943 item->num_categories, 944 item->categories)) 945 continue; 946 GNUNET_break_op (0); 947 use_reply_with_error ( 948 uc, 949 MHD_HTTP_CONFLICT, 950 TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_PRODUCT, 951 item->product_id); 952 return GNUNET_SYSERR; 953 } 954 return GNUNET_OK; 955 } 956 957 958 /** 959 * Verify request data for fixed-order templates. 960 * As here we cannot compute the total amount, either 961 * the template or the client request must provide it. 962 * 963 * @param[in,out] uc use context 964 * @return #GNUNET_OK on success 965 */ 966 static enum GNUNET_GenericReturnValue 967 verify_using_templates_fixed ( 968 struct UseContext *uc) 969 { 970 if ( (! uc->parse_request.no_amount) && 971 (! uc->template_contract.no_amount) ) 972 { 973 GNUNET_break_op (0); 974 use_reply_with_error (uc, 975 MHD_HTTP_CONFLICT, 976 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 977 NULL); 978 return GNUNET_SYSERR; 979 } 980 if (uc->parse_request.no_amount && 981 uc->template_contract.no_amount) 982 { 983 GNUNET_break_op (0); 984 use_reply_with_error (uc, 985 MHD_HTTP_CONFLICT, 986 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT, 987 NULL); 988 return GNUNET_SYSERR; 989 } 990 return GNUNET_OK; 991 } 992 993 994 /** 995 * Verify request data for paivana templates. 996 * 997 * @param[in,out] uc use context 998 * @return #GNUNET_OK on success 999 */ 1000 static enum GNUNET_GenericReturnValue 1001 verify_using_templates_paivana ( 1002 struct UseContext *uc) 1003 { 1004 if (NULL != uc->template_contract.details.paivana.website_regex) 1005 { 1006 regex_t ex; 1007 bool allowed = false; 1008 1009 if (0 != regcomp (&ex, 1010 uc->template_contract.details.paivana.website_regex, 1011 REG_NOSUB | REG_EXTENDED)) 1012 { 1013 GNUNET_break_op (0); 1014 return GNUNET_SYSERR; 1015 } 1016 if (0 == 1017 regexec (&ex, 1018 uc->parse_request.paivana.website, 1019 0, NULL, 1020 0)) 1021 { 1022 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1023 "Website `%s' allowed by template\n", 1024 uc->parse_request.paivana.website); 1025 allowed = true; 1026 } 1027 regfree (&ex); 1028 if (! allowed) 1029 { 1030 GNUNET_break_op (0); 1031 use_reply_with_error (uc, 1032 MHD_HTTP_BAD_REQUEST, 1033 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1034 "website_regex"); 1035 return GNUNET_SYSERR; 1036 } 1037 } 1038 return GNUNET_OK; 1039 } 1040 1041 1042 /** 1043 * Verify that the client request is structurally acceptable for the specified 1044 * template. Does NOT check the total amount being reasonable. 1045 * 1046 * @param[in,out] uc use context 1047 */ 1048 static void 1049 handle_phase_verify ( 1050 struct UseContext *uc) 1051 { 1052 enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; 1053 1054 if ( (NULL != uc->parse_request.summary) && 1055 (NULL != uc->template_contract.summary) ) 1056 { 1057 GNUNET_break_op (0); 1058 use_reply_with_error (uc, 1059 MHD_HTTP_CONFLICT, 1060 TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT, 1061 NULL); 1062 return; 1063 } 1064 if ( (NULL == uc->parse_request.summary) && 1065 (NULL == uc->template_contract.summary) ) 1066 { 1067 GNUNET_break_op (0); 1068 use_reply_with_error (uc, 1069 MHD_HTTP_CONFLICT, 1070 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY, 1071 NULL); 1072 return; 1073 } 1074 if ( (! uc->parse_request.no_amount) && 1075 (NULL != uc->template_contract.currency) && 1076 (0 != strcasecmp (uc->template_contract.currency, 1077 uc->parse_request.amount.currency)) ) 1078 { 1079 GNUNET_break_op (0); 1080 use_reply_with_error (uc, 1081 MHD_HTTP_CONFLICT, 1082 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1083 uc->template_contract.currency); 1084 return; 1085 } 1086 switch (uc->template_type) 1087 { 1088 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1089 res = verify_using_templates_fixed (uc); 1090 break; 1091 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1092 res = verify_using_templates_paivana (uc); 1093 break; 1094 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 1095 res = verify_using_templates_inventory (uc); 1096 break; 1097 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1098 GNUNET_assert (0); 1099 } 1100 if (GNUNET_OK == res) 1101 uc->phase++; 1102 } 1103 1104 1105 /* ***************** USE_PHASE_COMPUTE_PRICE **************** */ 1106 1107 1108 /** 1109 * Compute the line total for a product based on quantity. 1110 * 1111 * @param unit_price price per unit 1112 * @param quantity integer quantity 1113 * @param quantity_frac fractional quantity (0..TALER_MERCHANT_UNIT_FRAC_BASE-1) 1114 * @param[out] line_total resulting line total 1115 * @return #GNUNET_OK on success 1116 */ 1117 static enum GNUNET_GenericReturnValue 1118 compute_line_total (const struct TALER_Amount *unit_price, 1119 uint64_t quantity, 1120 uint32_t quantity_frac, 1121 struct TALER_Amount *line_total) 1122 { 1123 struct TALER_Amount tmp; 1124 1125 GNUNET_assert (GNUNET_OK == 1126 TALER_amount_set_zero (unit_price->currency, 1127 line_total)); 1128 if ( (0 != quantity) && 1129 (0 > 1130 TALER_amount_multiply (line_total, 1131 unit_price, 1132 (uint32_t) quantity)) ) 1133 { 1134 GNUNET_break (0); 1135 return GNUNET_SYSERR; 1136 } 1137 if (0 == quantity_frac) 1138 return GNUNET_OK; 1139 if (0 > 1140 TALER_amount_multiply (&tmp, 1141 unit_price, 1142 quantity_frac)) 1143 { 1144 GNUNET_break (0); 1145 return GNUNET_SYSERR; 1146 } 1147 TALER_amount_divide (&tmp, 1148 &tmp, 1149 TALER_MERCHANT_UNIT_FRAC_BASE); 1150 if (0 > 1151 TALER_amount_add (line_total, 1152 line_total, 1153 &tmp)) 1154 { 1155 GNUNET_break (0); 1156 return GNUNET_SYSERR; 1157 } 1158 return GNUNET_OK; 1159 } 1160 1161 1162 /** 1163 * Find the price of the given @a item in the specified 1164 * @a currency. 1165 * 1166 * @param currency currency to search price in 1167 * @param item item to check prices of 1168 * @return NULL if a suitable price was not found 1169 */ 1170 static const struct TALER_Amount * 1171 find_item_price_in_currency ( 1172 const char *currency, 1173 const struct InventoryTemplateItemContext *item) 1174 { 1175 for (size_t j = 0; j < item->pd.price_array_length; j++) 1176 { 1177 if (0 == strcasecmp (item->pd.price_array[j].currency, 1178 currency)) 1179 return &item->pd.price_array[j]; 1180 } 1181 return NULL; 1182 } 1183 1184 1185 /** 1186 * Compute totals for all currencies shared across selected products. 1187 * 1188 * @param[in,out] uc use context 1189 * @return #GNUNET_OK on success (including no price due to no items) 1190 * #GNUNET_NO if we could not find a price in any accepted currency 1191 * for all selected products 1192 * #GNUNET_SYSERR on arithmetic issues (internal error) 1193 */ 1194 static enum GNUNET_GenericReturnValue 1195 compute_totals_per_currency (struct UseContext *uc) 1196 { 1197 const struct InventoryTemplateItemContext *items 1198 = uc->parse_request.inventory.items; 1199 unsigned int items_len = uc->parse_request.inventory.items_len; 1200 1201 if (0 == items_len) 1202 return GNUNET_OK; 1203 for (size_t i = 0; i < items[0].pd.price_array_length; i++) 1204 { 1205 const struct TALER_Amount *price 1206 = &items[0].pd.price_array[i]; 1207 struct TALER_Amount zero; 1208 1209 if (! TMH_test_exchange_configured_for_currency (price->currency)) 1210 continue; 1211 GNUNET_assert (GNUNET_OK == 1212 TALER_amount_set_zero (price->currency, 1213 &zero)); 1214 GNUNET_array_append (uc->compute_price.totals, 1215 uc->compute_price.totals_len, 1216 zero); 1217 } 1218 if (0 == uc->compute_price.totals_len) 1219 { 1220 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1221 "No currency supported by our configuration in which we have prices for first selected product!\n"); 1222 return GNUNET_NO; 1223 } 1224 /* Loop through items, ensure each currency exists and sum totals. */ 1225 for (unsigned int i = 0; i < items_len; i++) 1226 { 1227 const struct InventoryTemplateItemContext *item = &items[i]; 1228 unsigned int c = 0; 1229 1230 while (c < uc->compute_price.totals_len) 1231 { 1232 struct TALER_Amount *total = &uc->compute_price.totals[c]; 1233 const struct TALER_Amount *unit_price; 1234 struct TALER_Amount line_total; 1235 1236 unit_price = find_item_price_in_currency (total->currency, 1237 item); 1238 if (NULL == unit_price) 1239 { 1240 /* Drop the currency: we have no price in one of 1241 the selected products */ 1242 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1243 "Product `%s' has no price in %s: dropping currency\n", 1244 item->product_id, 1245 total->currency); 1246 *total = uc->compute_price.totals[--uc->compute_price.totals_len]; 1247 continue; 1248 } 1249 if (GNUNET_OK != 1250 compute_line_total (unit_price, 1251 item->quantity_value, 1252 item->quantity_frac, 1253 &line_total)) 1254 { 1255 GNUNET_break (0); 1256 return GNUNET_SYSERR; 1257 } 1258 if (0 > 1259 TALER_amount_add (total, 1260 total, 1261 &line_total)) 1262 { 1263 GNUNET_break (0); 1264 return GNUNET_SYSERR; 1265 } 1266 c++; 1267 } 1268 } 1269 if (0 == uc->compute_price.totals_len) 1270 { 1271 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1272 "No currency available in which we have prices for all selected products!\n"); 1273 GNUNET_free (uc->compute_price.totals); 1274 } 1275 return (0 == uc->compute_price.totals_len) 1276 ? GNUNET_NO 1277 : GNUNET_OK; 1278 } 1279 1280 1281 /** 1282 * Compute total for only the given @a currency. 1283 * 1284 * @param items_len length of @a items 1285 * @param items inventory items 1286 * @param currency currency to total 1287 * @param[out] total computed total 1288 * @return #GNUNET_OK on success 1289 * #GNUNET_NO if we could not find a price in any accepted currency 1290 * for all selected products 1291 * #GNUNET_SYSERR on arithmetic issues (internal error) 1292 */ 1293 static enum GNUNET_GenericReturnValue 1294 compute_inventory_total (unsigned int items_len, 1295 const struct InventoryTemplateItemContext *items, 1296 const char *currency, 1297 struct TALER_Amount *total) 1298 { 1299 GNUNET_assert (NULL != currency); 1300 GNUNET_assert (GNUNET_OK == 1301 TALER_amount_set_zero (currency, 1302 total)); 1303 for (unsigned int i = 0; i < items_len; i++) 1304 { 1305 const struct InventoryTemplateItemContext *item = &items[i]; 1306 const struct TALER_Amount *unit_price; 1307 struct TALER_Amount line_total; 1308 1309 unit_price = find_item_price_in_currency (currency, 1310 item); 1311 if (NULL == unit_price) 1312 { 1313 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1314 "compute_inventory_total: no price in %s for product `%s'\n", 1315 currency, 1316 item->product_id); 1317 return GNUNET_NO; 1318 } 1319 if (GNUNET_OK != 1320 compute_line_total (unit_price, 1321 item->quantity_value, 1322 item->quantity_frac, 1323 &line_total)) 1324 { 1325 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1326 "compute_inventory_total: line total failed for %s in %s\n", 1327 item->product_id, 1328 currency); 1329 return GNUNET_SYSERR; 1330 } 1331 if (0 > 1332 TALER_amount_add (total, 1333 total, 1334 &line_total)) 1335 { 1336 GNUNET_break (0); 1337 return GNUNET_SYSERR; 1338 } 1339 } 1340 return GNUNET_OK; 1341 } 1342 1343 1344 /** 1345 * Compute total price. 1346 * 1347 * @param[in,out] uc use context 1348 */ 1349 static void 1350 handle_phase_compute_price (struct UseContext *uc) 1351 { 1352 const char *primary_currency; 1353 enum GNUNET_GenericReturnValue ret; 1354 1355 switch (uc->template_type) 1356 { 1357 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1358 uc->compute_price.totals 1359 = GNUNET_new (struct TALER_Amount); 1360 uc->compute_price.totals_len 1361 = 1; 1362 if (uc->parse_request.no_amount) 1363 { 1364 GNUNET_assert (! uc->template_contract.no_amount); 1365 *uc->compute_price.totals 1366 = uc->template_contract.amount; 1367 } 1368 else 1369 { 1370 GNUNET_assert (uc->template_contract.no_amount); 1371 *uc->compute_price.totals 1372 = uc->parse_request.amount; 1373 } 1374 uc->phase++; 1375 return; 1376 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 1377 /* handled below */ 1378 break; 1379 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1380 { 1381 const struct TALER_MERCHANT_TemplateContractPaivana *tcp 1382 = &uc->template_contract.details.paivana; 1383 json_t *choices; 1384 1385 choices = json_array (); 1386 GNUNET_assert (NULL != choices); 1387 for (size_t i = 0; i < tcp->choices_len; i++) 1388 { 1389 /* Make deep copy, we're going to MODIFY it! */ 1390 struct TALER_MERCHANT_OrderChoice choice 1391 = tcp->choices[i]; 1392 1393 choice.no_tip = uc->parse_request.no_tip; 1394 if (! uc->parse_request.no_tip) 1395 { 1396 if (GNUNET_YES != 1397 TALER_amount_cmp_currency (&choice.amount, 1398 &uc->parse_request.tip)) 1399 continue; /* tip does not match choice currency */ 1400 choice.tip = uc->parse_request.tip; 1401 if (0 > 1402 TALER_amount_add (&choice.amount, 1403 &choice.amount, 1404 &uc->parse_request.tip)) 1405 { 1406 GNUNET_break (0); 1407 use_reply_with_error (uc, 1408 MHD_HTTP_INTERNAL_SERVER_ERROR, 1409 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1410 "tip"); 1411 return; 1412 } 1413 } 1414 GNUNET_assert (0 == 1415 json_array_append_new ( 1416 choices, 1417 TALER_MERCHANT_json_from_order_choice (&choice))); 1418 } 1419 if (0 == json_array_size (choices)) 1420 { 1421 GNUNET_break_op (0); 1422 json_decref (choices); 1423 use_reply_with_error (uc, 1424 MHD_HTTP_CONFLICT, 1425 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY, 1426 "tip"); 1427 return; 1428 } 1429 uc->compute_price.choices = choices; 1430 } 1431 /* Note: we already did the tip and pricing 1432 fully here, so we skip these phases. */ 1433 uc->phase = USE_PHASE_CREATE_ORDER; 1434 return; 1435 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1436 GNUNET_assert (0); 1437 } 1438 primary_currency = uc->template_contract.currency; 1439 if (! uc->parse_request.no_amount) 1440 primary_currency = uc->parse_request.amount.currency; 1441 if (! uc->parse_request.no_tip) 1442 primary_currency = uc->parse_request.tip.currency; 1443 if (NULL == primary_currency) 1444 { 1445 ret = compute_totals_per_currency (uc); 1446 } 1447 else 1448 { 1449 uc->compute_price.totals 1450 = GNUNET_new (struct TALER_Amount); 1451 uc->compute_price.totals_len 1452 = 1; 1453 ret = compute_inventory_total ( 1454 uc->parse_request.inventory.items_len, 1455 uc->parse_request.inventory.items, 1456 primary_currency, 1457 uc->compute_price.totals); 1458 } 1459 if (GNUNET_SYSERR == ret) 1460 { 1461 use_reply_with_error ( 1462 uc, 1463 MHD_HTTP_INTERNAL_SERVER_ERROR, 1464 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1465 "calculation of currency totals failed"); 1466 return; 1467 } 1468 if (GNUNET_NO == ret) 1469 { 1470 use_reply_with_error (uc, 1471 MHD_HTTP_CONFLICT, 1472 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY, 1473 NULL); 1474 return; 1475 } 1476 1477 uc->phase++; 1478 } 1479 1480 1481 /* ***************** USE_PHASE_CHECK_TIP **************** */ 1482 1483 1484 /** 1485 * Check that tip specified is reasonable and add to total. 1486 * 1487 * @param[in,out] uc use context 1488 */ 1489 static void 1490 handle_phase_check_tip (struct UseContext *uc) 1491 { 1492 struct TALER_Amount *total_amount; 1493 1494 if (uc->parse_request.no_tip) 1495 { 1496 uc->phase++; 1497 return; 1498 } 1499 if (0 == uc->compute_price.totals_len) 1500 { 1501 if (! TMH_test_exchange_configured_for_currency ( 1502 uc->parse_request.tip.currency)) 1503 { 1504 GNUNET_break_op (0); 1505 use_reply_with_error (uc, 1506 MHD_HTTP_CONFLICT, 1507 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1508 "Tip currency is not supported by backend"); 1509 return; 1510 } 1511 uc->compute_price.totals 1512 = GNUNET_new (struct TALER_Amount); 1513 uc->compute_price.totals_len 1514 = 1; 1515 *uc->compute_price.totals 1516 = uc->parse_request.tip; 1517 uc->phase++; 1518 return; 1519 } 1520 GNUNET_assert (1 == uc->compute_price.totals_len); 1521 total_amount = &uc->compute_price.totals[0]; 1522 if (GNUNET_YES != 1523 TALER_amount_cmp_currency (&uc->parse_request.tip, 1524 total_amount)) 1525 { 1526 GNUNET_break_op (0); 1527 use_reply_with_error (uc, 1528 MHD_HTTP_CONFLICT, 1529 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1530 uc->parse_request.tip.currency); 1531 return; 1532 } 1533 if (0 > 1534 TALER_amount_add (total_amount, 1535 total_amount, 1536 &uc->parse_request.tip)) 1537 { 1538 GNUNET_break (0); 1539 use_reply_with_error (uc, 1540 MHD_HTTP_INTERNAL_SERVER_ERROR, 1541 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1542 "tip"); 1543 return; 1544 } 1545 uc->phase++; 1546 } 1547 1548 1549 /* ***************** USE_PHASE_CHECK_TOTAL **************** */ 1550 1551 /** 1552 * Check that if the client specified a total, 1553 * it matches our own calculation. 1554 * 1555 * @param[in,out] uc use context 1556 */ 1557 static void 1558 handle_phase_check_total (struct UseContext *uc) 1559 { 1560 GNUNET_assert (1 <= uc->compute_price.totals_len); 1561 if (! uc->parse_request.no_amount) 1562 { 1563 GNUNET_assert (1 == uc->compute_price.totals_len); 1564 GNUNET_assert (GNUNET_YES == 1565 TALER_amount_cmp_currency (&uc->parse_request.amount, 1566 &uc->compute_price.totals[0])); 1567 if (0 != 1568 TALER_amount_cmp (&uc->parse_request.amount, 1569 &uc->compute_price.totals[0])) 1570 { 1571 GNUNET_break_op (0); 1572 use_reply_with_error (uc, 1573 MHD_HTTP_CONFLICT, 1574 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 1575 TALER_amount2s (&uc->compute_price.totals[0])); 1576 return; 1577 } 1578 } 1579 uc->phase++; 1580 } 1581 1582 1583 /* ***************** USE_PHASE_CREATE_ORDER **************** */ 1584 1585 1586 /** 1587 * Create order request for inventory templates. 1588 * 1589 * @param[in,out] uc use context 1590 */ 1591 static void 1592 create_using_templates_inventory (struct UseContext *uc) 1593 { 1594 json_t *inventory_products; 1595 json_t *choices; 1596 1597 inventory_products = json_array (); 1598 GNUNET_assert (NULL != inventory_products); 1599 for (unsigned int i = 0; 1600 i < uc->parse_request.inventory.items_len; 1601 i++) 1602 { 1603 const struct InventoryTemplateItemContext *item = 1604 &uc->parse_request.inventory.items[i]; 1605 1606 GNUNET_assert (0 == 1607 json_array_append_new ( 1608 inventory_products, 1609 GNUNET_JSON_PACK ( 1610 GNUNET_JSON_pack_string ("product_id", 1611 item->product_id), 1612 GNUNET_JSON_pack_string ("unit_quantity", 1613 item->unit_quantity)))); 1614 } 1615 choices = json_array (); 1616 GNUNET_assert (NULL != choices); 1617 for (unsigned int i = 0; 1618 i < uc->compute_price.totals_len; 1619 i++) 1620 { 1621 GNUNET_assert (0 == 1622 json_array_append_new ( 1623 choices, 1624 GNUNET_JSON_PACK ( 1625 TALER_JSON_pack_amount ("amount", 1626 &uc->compute_price.totals[i]), 1627 GNUNET_JSON_pack_allow_null ( 1628 TALER_JSON_pack_amount ("tip", 1629 uc->parse_request.no_tip 1630 ? NULL 1631 : &uc->parse_request.tip)) 1632 ))); 1633 } 1634 1635 uc->ihc.request_body 1636 = GNUNET_JSON_PACK ( 1637 GNUNET_JSON_pack_allow_null ( 1638 GNUNET_JSON_pack_string ("otp_id", 1639 uc->lookup_template.etp.otp_id)), 1640 GNUNET_JSON_pack_array_steal ("inventory_products", 1641 inventory_products), 1642 GNUNET_JSON_pack_object_steal ( 1643 "order", 1644 GNUNET_JSON_PACK ( 1645 GNUNET_JSON_pack_uint64 ("version", 1646 1), 1647 GNUNET_JSON_pack_array_steal ("choices", 1648 choices), 1649 GNUNET_JSON_pack_string ("summary", 1650 NULL == uc->parse_request.summary 1651 ? uc->template_contract.summary 1652 : uc->parse_request.summary)))); 1653 if (! GNUNET_TIME_relative_is_forever ( 1654 uc->template_contract.max_pickup_duration)) 1655 { 1656 GNUNET_assert ( 1657 0 == 1658 json_object_set_new ( 1659 uc->ihc.request_body, 1660 "max_pickup_time", 1661 GNUNET_JSON_from_timestamp ( 1662 GNUNET_TIME_absolute_to_timestamp ( 1663 GNUNET_TIME_relative_to_absolute ( 1664 uc->template_contract.max_pickup_duration))))); 1665 } 1666 } 1667 1668 1669 /** 1670 * Create order request for fixed-order templates. 1671 * 1672 * @param[in,out] uc use context 1673 */ 1674 static void 1675 create_using_templates_fixed (struct UseContext *uc) 1676 { 1677 uc->ihc.request_body 1678 = GNUNET_JSON_PACK ( 1679 GNUNET_JSON_pack_allow_null ( 1680 GNUNET_JSON_pack_string ("otp_id", 1681 uc->lookup_template.etp.otp_id)), 1682 GNUNET_JSON_pack_object_steal ( 1683 "order", 1684 GNUNET_JSON_PACK ( 1685 TALER_JSON_pack_amount ( 1686 "amount", 1687 &uc->compute_price.totals[0]), 1688 GNUNET_JSON_pack_allow_null ( 1689 TALER_JSON_pack_amount ("tip", 1690 uc->parse_request.no_tip 1691 ? NULL 1692 : &uc->parse_request.tip)), 1693 GNUNET_JSON_pack_string ( 1694 "summary", 1695 NULL == uc->parse_request.summary 1696 ? uc->template_contract.summary 1697 : uc->parse_request.summary)))); 1698 } 1699 1700 1701 /** 1702 * Create order request for paivana templates. 1703 * 1704 * @param[in,out] uc use context 1705 */ 1706 static void 1707 create_using_templates_paivana (struct UseContext *uc) 1708 { 1709 uc->ihc.request_body 1710 = GNUNET_JSON_PACK ( 1711 GNUNET_JSON_pack_string ( 1712 "session_id", 1713 uc->parse_request.paivana.paivana_id), 1714 GNUNET_JSON_pack_object_steal ( 1715 "order", 1716 GNUNET_JSON_PACK ( 1717 GNUNET_JSON_pack_uint64 ("version", 1718 1), 1719 GNUNET_JSON_pack_array_incref ("choices", 1720 uc->compute_price.choices), 1721 GNUNET_JSON_pack_string ( 1722 "summary", 1723 NULL == uc->parse_request.summary 1724 ? uc->template_contract.summary 1725 : uc->parse_request.summary), 1726 GNUNET_JSON_pack_string ("fulfillment_url", 1727 uc->parse_request.paivana.website)))); 1728 } 1729 1730 1731 static void 1732 handle_phase_create_order (struct UseContext *uc) 1733 { 1734 GNUNET_assert (NULL == uc->ihc.request_body); 1735 switch (uc->template_type) 1736 { 1737 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1738 create_using_templates_fixed (uc); 1739 break; 1740 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1741 create_using_templates_paivana (uc); 1742 break; 1743 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 1744 create_using_templates_inventory (uc); 1745 break; 1746 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1747 GNUNET_assert (0); 1748 } 1749 uc->phase++; 1750 } 1751 1752 1753 /* ***************** Main handler **************** */ 1754 1755 enum MHD_Result 1756 TMH_post_using_templates_ID ( 1757 const struct TMH_RequestHandler *rh, 1758 struct MHD_Connection *connection, 1759 struct TMH_HandlerContext *hc) 1760 { 1761 struct UseContext *uc = hc->ctx; 1762 1763 (void) rh; 1764 if (NULL == uc) 1765 { 1766 uc = GNUNET_new (struct UseContext); 1767 uc->hc = hc; 1768 hc->ctx = uc; 1769 hc->cc = &cleanup_use_context; 1770 uc->ihc.instance = hc->instance; 1771 uc->phase = USE_PHASE_PARSE_REQUEST; 1772 uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID; 1773 } 1774 1775 while (1) 1776 { 1777 switch (uc->phase) 1778 { 1779 case USE_PHASE_PARSE_REQUEST: 1780 handle_phase_parse_request (uc); 1781 break; 1782 case USE_PHASE_LOOKUP_TEMPLATE: 1783 handle_phase_lookup_template (uc); 1784 break; 1785 case USE_PHASE_PARSE_TEMPLATE: 1786 handle_phase_template_contract (uc); 1787 break; 1788 case USE_PHASE_DB_FETCH: 1789 handle_phase_db_fetch (uc); 1790 break; 1791 case USE_PHASE_VERIFY: 1792 handle_phase_verify (uc); 1793 break; 1794 case USE_PHASE_COMPUTE_PRICE: 1795 handle_phase_compute_price (uc); 1796 break; 1797 case USE_PHASE_CHECK_TIP: 1798 handle_phase_check_tip (uc); 1799 break; 1800 case USE_PHASE_CHECK_TOTAL: 1801 handle_phase_check_total (uc); 1802 break; 1803 case USE_PHASE_CREATE_ORDER: 1804 handle_phase_create_order (uc); 1805 break; 1806 case USE_PHASE_SUBMIT_ORDER: 1807 return TMH_private_post_orders ( 1808 NULL, /* not even used */ 1809 connection, 1810 &uc->ihc); 1811 case USE_PHASE_FINISHED_MHD_YES: 1812 return MHD_YES; 1813 case USE_PHASE_FINISHED_MHD_NO: 1814 return MHD_NO; 1815 } 1816 } 1817 }