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