taler-merchant-httpd_post-templates-TEMPLATE_ID.c (65893B)
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/get_product.h" 36 #include "merchant-database/get_template.h" 37 38 39 /** 40 * Maximum number of entries we accept in the @e inventory_selection 41 * array of a request. Each entry costs us one database round-trip 42 * and a full copy of the product details (including the base64-encoded 43 * product image), so this must be bounded independently of the maximum 44 * upload size. 45 */ 46 #define MAX_INVENTORY_SELECTION 1024 47 48 49 /** 50 * Amount the client chose for one of the choices of a paivana template. 51 */ 52 struct PaivanaChoiceAmount 53 { 54 /** 55 * Index into the @e choices array of the template contract. 56 */ 57 uint32_t choice_index; 58 59 /** 60 * Amount to use for that choice, excluding any tip. 61 */ 62 struct TALER_Amount amount; 63 }; 64 65 66 /** 67 * Item selected from inventory_selection. 68 */ 69 struct InventoryTemplateItemContext 70 { 71 /** 72 * Product ID as referenced in inventory. 73 */ 74 const char *product_id; 75 76 /** 77 * Unit quantity string as provided by the client. 78 */ 79 const char *unit_quantity; 80 81 /** 82 * Parsed integer quantity. 83 */ 84 uint64_t quantity_value; 85 86 /** 87 * Parsed fractional quantity. 88 */ 89 uint32_t quantity_frac; 90 91 /** 92 * Product details from the DB (includes price array). 93 */ 94 struct TALER_MERCHANTDB_ProductDetails pd; 95 96 /** 97 * Categories referenced by the product. 98 */ 99 uint64_t *categories; 100 101 /** 102 * Length of @e categories. 103 */ 104 size_t num_categories; 105 }; 106 107 108 /** 109 * Our context. 110 */ 111 enum UsePhase 112 { 113 /** 114 * Parse request payload into context fields. 115 */ 116 USE_PHASE_PARSE_REQUEST, 117 118 /** 119 * Fetch template details from the database. 120 */ 121 USE_PHASE_LOOKUP_TEMPLATE, 122 123 /** 124 * Parse template. 125 */ 126 USE_PHASE_PARSE_TEMPLATE, 127 128 /** 129 * Load additional details (like products and 130 * categories) needed for verification and 131 * price computation. 132 */ 133 USE_PHASE_DB_FETCH, 134 135 /** 136 * Validate request and template compatibility. 137 */ 138 USE_PHASE_VERIFY, 139 140 /** 141 * Compute price of the order. 142 */ 143 USE_PHASE_COMPUTE_PRICE, 144 145 /** 146 * Handle tip. 147 */ 148 USE_PHASE_CHECK_TIP, 149 150 /** 151 * Check if client-supplied total amount matches 152 * our calculation (if we did any). 153 */ 154 USE_PHASE_CHECK_TOTAL, 155 156 /** 157 * Construct the internal order request body. 158 */ 159 USE_PHASE_CREATE_ORDER, 160 161 /** 162 * Submit the order to the shared order handler. 163 */ 164 USE_PHASE_SUBMIT_ORDER, 165 166 /** 167 * Finished successfully with MHD_YES. 168 */ 169 USE_PHASE_FINISHED_MHD_YES, 170 171 /** 172 * Finished with MHD_NO. 173 */ 174 USE_PHASE_FINISHED_MHD_NO 175 }; 176 177 struct UseContext 178 { 179 /** 180 * Context for our handler. 181 */ 182 struct TMH_HandlerContext *hc; 183 184 /** 185 * Internal handler context we are passing into the 186 * POST /private/orders handler. 187 */ 188 struct TMH_HandlerContext ihc; 189 190 /** 191 * Phase we are currently in. 192 */ 193 enum UsePhase phase; 194 195 /** 196 * Template type from the contract. 197 */ 198 enum TALER_MERCHANT_TemplateType template_type; 199 200 /** 201 * Information set in the #USE_PHASE_PARSE_REQUEST phase. 202 */ 203 struct 204 { 205 /** 206 * Summary override from request, if any. 207 */ 208 const char *summary; 209 210 /** 211 * Amount provided by the client. 212 */ 213 struct TALER_Amount amount; 214 215 /** 216 * Tip provided by the client. 217 */ 218 struct TALER_Amount tip; 219 220 /** 221 * True if @e amount was not provided. 222 */ 223 bool no_amount; 224 225 /** 226 * True if @e tip was not provided. 227 */ 228 bool no_tip; 229 230 /** 231 * Parsed fields for inventory templates. 232 */ 233 struct 234 { 235 /** 236 * Selected products from inventory_selection. 237 */ 238 struct InventoryTemplateItemContext *items; 239 240 /** 241 * Length of @e items. 242 */ 243 unsigned int items_len; 244 245 } inventory; 246 247 /** 248 * Request details if this is a paivana instantiation. 249 */ 250 struct 251 { 252 253 /** 254 * Target website for the request. 255 */ 256 const char *website; 257 258 /** 259 * Unique client identifier, consisting of 260 * current time, "-", and the hash of a nonce, 261 * the website and the current time. 262 */ 263 const char *paivana_id; 264 265 /** 266 * Amounts the client picked for those choices of the 267 * template that allow the amount to be edited. 268 */ 269 struct PaivanaChoiceAmount *choice_amounts; 270 271 /** 272 * Length of the @e choice_amounts array. 273 */ 274 unsigned int choice_amounts_len; 275 276 } paivana; 277 278 } parse_request; 279 280 /** 281 * Information set in the #USE_PHASE_LOOKUP_TEMPLATE phase. 282 */ 283 struct 284 { 285 286 /** 287 * Our template details from the DB. 288 */ 289 struct TALER_MERCHANTDB_TemplateDetails etp; 290 291 } lookup_template; 292 293 /** 294 * Information set in the #USE_PHASE_PARSE_TEMPLATE phase. 295 */ 296 struct TALER_MERCHANT_TemplateContract template_contract; 297 298 /** 299 * Information set in the #USE_PHASE_COMPUTE_PRICE phase. 300 */ 301 struct 302 { 303 304 /** 305 * Per-currency totals across selected products (without tips). 306 */ 307 struct TALER_Amount *totals; 308 309 /** 310 * Length of @e totals. 311 */ 312 unsigned int totals_len; 313 314 /** 315 * Array of payment choices, used with Paviana. 316 */ 317 json_t *choices; 318 319 } compute_price; 320 321 }; 322 323 324 /** 325 * Clean up inventory items. 326 * 327 * @param items_len length of @a items 328 * @param[in] items item array to free 329 */ 330 static void 331 cleanup_inventory_items ( 332 unsigned int items_len, 333 struct InventoryTemplateItemContext items[static items_len]) 334 { 335 for (unsigned int i = 0; i < items_len; i++) 336 { 337 struct InventoryTemplateItemContext *item = &items[i]; 338 339 TALER_MERCHANTDB_product_details_free (&item->pd); 340 GNUNET_free (item->categories); 341 } 342 GNUNET_free (items); 343 } 344 345 346 /** 347 * Clean up a `struct UseContext *` 348 * 349 * @param[in] cls a `struct UseContext *` 350 */ 351 static void 352 cleanup_use_context (void *cls) 353 { 354 struct UseContext *uc = cls; 355 356 TALER_MERCHANTDB_template_details_free (&uc->lookup_template.etp); 357 if (NULL != 358 uc->parse_request.inventory.items) 359 cleanup_inventory_items (uc->parse_request.inventory.items_len, 360 uc->parse_request.inventory.items); 361 GNUNET_array_grow (uc->parse_request.paivana.choice_amounts, 362 uc->parse_request.paivana.choice_amounts_len, 363 0); 364 TALER_MERCHANT_template_contract_free (&uc->template_contract); 365 GNUNET_free (uc->compute_price.totals); 366 uc->compute_price.totals_len = 0; 367 json_decref (uc->compute_price.choices); 368 if (NULL != uc->ihc.cc) 369 uc->ihc.cc (uc->ihc.ctx); 370 GNUNET_free (uc->ihc.infix); 371 json_decref (uc->ihc.request_body); 372 GNUNET_free (uc); 373 } 374 375 376 /** 377 * Finalize a template use request. 378 * 379 * @param[in,out] uc use context 380 * @param ret handler return value 381 */ 382 static void 383 use_finalize (struct UseContext *uc, 384 enum MHD_Result ret) 385 { 386 uc->phase = (MHD_YES == ret) 387 ? USE_PHASE_FINISHED_MHD_YES 388 : USE_PHASE_FINISHED_MHD_NO; 389 } 390 391 392 /** 393 * Finalize after JSON parsing result. 394 * 395 * @param[in,out] uc use context 396 * @param res parse result 397 */ 398 static void 399 use_finalize_parse (struct UseContext *uc, 400 enum GNUNET_GenericReturnValue res) 401 { 402 GNUNET_assert (GNUNET_OK != res); 403 use_finalize (uc, 404 (GNUNET_NO == res) 405 ? MHD_YES 406 : MHD_NO); 407 } 408 409 410 /** 411 * Reply with error and finalize the request. 412 * 413 * @param[in,out] uc use context 414 * @param http_status HTTP status code 415 * @param ec error code 416 * @param detail error detail 417 */ 418 static void 419 use_reply_with_error (struct UseContext *uc, 420 unsigned int http_status, 421 enum TALER_ErrorCode ec, 422 const char *detail) 423 { 424 enum MHD_Result mret; 425 426 mret = TALER_MHD_reply_with_error (uc->hc->connection, 427 http_status, 428 ec, 429 detail); 430 use_finalize (uc, 431 mret); 432 } 433 434 435 /* ***************** USE_PHASE_PARSE_REQUEST **************** */ 436 437 /** 438 * Parse request data for inventory templates. 439 * 440 * @param[in,out] uc use context 441 * @return #GNUNET_OK on success 442 */ 443 static enum GNUNET_GenericReturnValue 444 parse_using_templates_inventory_request ( 445 struct UseContext *uc) 446 { 447 const json_t *inventory_selection; 448 struct GNUNET_JSON_Specification spec[] = { 449 GNUNET_JSON_spec_array_const ("inventory_selection", 450 &inventory_selection), 451 GNUNET_JSON_spec_end () 452 }; 453 enum GNUNET_GenericReturnValue res; 454 455 GNUNET_assert (NULL == uc->ihc.request_body); 456 res = TALER_MHD_parse_json_data (uc->hc->connection, 457 uc->hc->request_body, 458 spec); 459 if (GNUNET_OK != res) 460 { 461 GNUNET_break_op (0); 462 use_finalize_parse (uc, 463 res); 464 return GNUNET_SYSERR; 465 } 466 467 if ( (! uc->parse_request.no_amount) && 468 (! TMH_test_exchange_configured_for_currency ( 469 uc->parse_request.amount.currency)) ) 470 { 471 GNUNET_break_op (0); 472 use_reply_with_error (uc, 473 MHD_HTTP_CONFLICT, 474 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 475 "Currency is not supported by backend"); 476 return GNUNET_SYSERR; 477 } 478 479 if (MAX_INVENTORY_SELECTION < json_array_size (inventory_selection)) 480 { 481 GNUNET_break_op (0); 482 use_reply_with_error (uc, 483 MHD_HTTP_BAD_REQUEST, 484 TALER_EC_GENERIC_PARAMETER_MALFORMED, 485 "inventory_selection (too many entries)"); 486 return GNUNET_SYSERR; 487 } 488 for (size_t i = 0; i < json_array_size (inventory_selection); i++) 489 { 490 struct InventoryTemplateItemContext item = { 0 }; 491 struct GNUNET_JSON_Specification ispec[] = { 492 TALER_JSON_spec_slug ("product_id", 493 &item.product_id), 494 GNUNET_JSON_spec_string ("quantity", 495 &item.unit_quantity), 496 GNUNET_JSON_spec_end () 497 }; 498 const char *err_name; 499 unsigned int err_line; 500 501 res = GNUNET_JSON_parse (json_array_get (inventory_selection, 502 i), 503 ispec, 504 &err_name, 505 &err_line); 506 if (GNUNET_OK != res) 507 { 508 GNUNET_break_op (0); 509 use_reply_with_error (uc, 510 MHD_HTTP_BAD_REQUEST, 511 TALER_EC_GENERIC_PARAMETER_MALFORMED, 512 "inventory_selection"); 513 return GNUNET_SYSERR; 514 } 515 516 GNUNET_array_append (uc->parse_request.inventory.items, 517 uc->parse_request.inventory.items_len, 518 item); 519 } 520 return GNUNET_OK; 521 } 522 523 524 /** 525 * Parse request data for paivana templates. 526 * 527 * @param[in,out] uc use context 528 * @return #GNUNET_OK on success 529 */ 530 static enum GNUNET_GenericReturnValue 531 parse_using_templates_paivana_request ( 532 struct UseContext *uc) 533 { 534 const json_t *choice_amounts = NULL; 535 struct GNUNET_JSON_Specification spec[] = { 536 TALER_JSON_spec_web_url ("website", 537 &uc->parse_request.paivana.website), 538 GNUNET_JSON_spec_string ("paivana_id", 539 &uc->parse_request.paivana.paivana_id), 540 GNUNET_JSON_spec_mark_optional ( 541 GNUNET_JSON_spec_array_const ("choice_amounts", 542 &choice_amounts), 543 NULL), 544 GNUNET_JSON_spec_end () 545 }; 546 enum GNUNET_GenericReturnValue res; 547 unsigned long long tv; 548 const char *dash; 549 550 GNUNET_assert (NULL == uc->ihc.request_body); 551 res = TALER_MHD_parse_json_data (uc->hc->connection, 552 uc->hc->request_body, 553 spec); 554 if (GNUNET_OK != res) 555 { 556 GNUNET_break_op (0); 557 use_finalize_parse (uc, 558 res); 559 return GNUNET_SYSERR; 560 } 561 if (NULL != choice_amounts) 562 { 563 if (! uc->parse_request.no_amount) 564 { 565 /* 'amount' is the shorthand for a template with a single 566 editable choice; using both is ambiguous. */ 567 GNUNET_break_op (0); 568 use_reply_with_error ( 569 uc, 570 MHD_HTTP_CONFLICT, 571 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 572 "amount and choice_amounts are mutually exclusive"); 573 return GNUNET_SYSERR; 574 } 575 for (size_t i = 0; i < json_array_size (choice_amounts); i++) 576 { 577 struct PaivanaChoiceAmount ca; 578 struct GNUNET_JSON_Specification ispec[] = { 579 GNUNET_JSON_spec_uint32 ("choice_index", 580 &ca.choice_index), 581 TALER_JSON_spec_amount_any ("amount", 582 &ca.amount), 583 GNUNET_JSON_spec_end () 584 }; 585 const char *err_name; 586 unsigned int err_line; 587 588 if (GNUNET_OK != 589 GNUNET_JSON_parse (json_array_get (choice_amounts, 590 i), 591 ispec, 592 &err_name, 593 &err_line)) 594 { 595 GNUNET_break_op (0); 596 use_reply_with_error (uc, 597 MHD_HTTP_BAD_REQUEST, 598 TALER_EC_GENERIC_PARAMETER_MALFORMED, 599 "choice_amounts"); 600 return GNUNET_SYSERR; 601 } 602 for (unsigned int j = 0; 603 j < uc->parse_request.paivana.choice_amounts_len; 604 j++) 605 { 606 if (uc->parse_request.paivana.choice_amounts[j].choice_index != 607 ca.choice_index) 608 continue; 609 GNUNET_break_op (0); 610 use_reply_with_error (uc, 611 MHD_HTTP_BAD_REQUEST, 612 TALER_EC_GENERIC_PARAMETER_MALFORMED, 613 "choice_amounts::choice_index is not unique"); 614 return GNUNET_SYSERR; 615 } 616 /* The currency does not need to be checked against our 617 configuration here: it must match the currency of the 618 choice in the template, which the merchant picked. */ 619 GNUNET_array_append (uc->parse_request.paivana.choice_amounts, 620 uc->parse_request.paivana.choice_amounts_len, 621 ca); 622 } 623 } 624 if (! TALER_is_session_id (uc->parse_request.paivana.paivana_id)) 625 { 626 /* The Paivana ID becomes the session ID of the order, and thus 627 ends up as a path component of the "taler://pay/" URI; the 628 base64url decoding below is too lenient to ensure this. */ 629 GNUNET_break_op (0); 630 use_reply_with_error (uc, 631 MHD_HTTP_BAD_REQUEST, 632 TALER_EC_GENERIC_PARAMETER_MALFORMED, 633 "paivana_id"); 634 return GNUNET_SYSERR; 635 } 636 if (1 != 637 sscanf (uc->parse_request.paivana.paivana_id, 638 "%llu-", 639 &tv)) 640 { 641 GNUNET_break_op (0); 642 use_reply_with_error (uc, 643 MHD_HTTP_BAD_REQUEST, 644 TALER_EC_GENERIC_PARAMETER_MALFORMED, 645 "paivana_id"); 646 return GNUNET_SYSERR; 647 } 648 dash = strchr (uc->parse_request.paivana.paivana_id, 649 '-'); 650 if (NULL == dash) 651 { 652 GNUNET_break_op (0); 653 use_reply_with_error (uc, 654 MHD_HTTP_BAD_REQUEST, 655 TALER_EC_GENERIC_PARAMETER_MALFORMED, 656 "paivana_id"); 657 return GNUNET_SYSERR; 658 } 659 { 660 size_t olen; 661 void *out = NULL; 662 663 olen = GNUNET_STRINGS_base64url_decode (dash + 1, 664 strlen (dash + 1), 665 &out); 666 GNUNET_free (out); 667 if (sizeof (struct GNUNET_ShortHashCode) != olen) 668 { 669 GNUNET_break_op (0); 670 use_reply_with_error (uc, 671 MHD_HTTP_BAD_REQUEST, 672 TALER_EC_GENERIC_PARAMETER_MALFORMED, 673 "paivana_id"); 674 return GNUNET_SYSERR; 675 } 676 } 677 return GNUNET_OK; 678 } 679 680 681 /** 682 * Main function for the #USE_PHASE_PARSE_REQUEST. 683 * 684 * @param[in,out] uc context to update 685 */ 686 static void 687 handle_phase_parse_request ( 688 struct UseContext *uc) 689 { 690 const char *template_type = NULL; 691 struct GNUNET_JSON_Specification spec[] = { 692 GNUNET_JSON_spec_mark_optional ( 693 GNUNET_JSON_spec_string ("template_type", 694 &template_type), 695 NULL), 696 GNUNET_JSON_spec_mark_optional ( 697 TALER_JSON_spec_amount_any ("tip", 698 &uc->parse_request.tip), 699 &uc->parse_request.no_tip), 700 GNUNET_JSON_spec_mark_optional ( 701 GNUNET_JSON_spec_string ("summary", 702 &uc->parse_request.summary), 703 NULL), 704 GNUNET_JSON_spec_mark_optional ( 705 TALER_JSON_spec_amount_any ("amount", 706 &uc->parse_request.amount), 707 &uc->parse_request.no_amount), 708 GNUNET_JSON_spec_end () 709 }; 710 enum GNUNET_GenericReturnValue res; 711 712 res = TALER_MHD_parse_json_data (uc->hc->connection, 713 uc->hc->request_body, 714 spec); 715 if (GNUNET_OK != res) 716 { 717 GNUNET_break_op (0); 718 use_finalize_parse (uc, 719 res); 720 return; 721 } 722 if (NULL == template_type) 723 template_type = "fixed-order"; 724 uc->template_type 725 = TALER_MERCHANT_template_type_from_string ( 726 template_type); 727 switch (uc->template_type) 728 { 729 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 730 /* nothig left to do */ 731 uc->phase++; 732 return; 733 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 734 res = parse_using_templates_paivana_request (uc); 735 if (GNUNET_OK == res) 736 uc->phase++; 737 return; 738 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 739 res = parse_using_templates_inventory_request (uc); 740 if (GNUNET_OK == res) 741 uc->phase++; 742 return; 743 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 744 break; 745 } 746 GNUNET_break (0); 747 use_reply_with_error ( 748 uc, 749 MHD_HTTP_BAD_REQUEST, 750 TALER_EC_GENERIC_PARAMETER_MALFORMED, 751 "template_type"); 752 } 753 754 755 /* ***************** USE_PHASE_LOOKUP_TEMPLATE **************** */ 756 757 /** 758 * Main function for the #USE_PHASE_LOOKUP_TEMPLATE. 759 * 760 * @param[in,out] uc context to update 761 */ 762 static void 763 handle_phase_lookup_template ( 764 struct UseContext *uc) 765 { 766 struct TMH_MerchantInstance *mi = uc->hc->instance; 767 const char *template_id = uc->hc->infix; 768 enum GNUNET_DB_QueryStatus qs; 769 770 qs = TALER_MERCHANTDB_get_template (TMH_db, 771 mi->settings.id, 772 template_id, 773 &uc->lookup_template.etp); 774 switch (qs) 775 { 776 case GNUNET_DB_STATUS_HARD_ERROR: 777 /* Clean up and fail hard */ 778 GNUNET_break (0); 779 use_reply_with_error (uc, 780 MHD_HTTP_INTERNAL_SERVER_ERROR, 781 TALER_EC_GENERIC_DB_FETCH_FAILED, 782 "get_template"); 783 return; 784 case GNUNET_DB_STATUS_SOFT_ERROR: 785 /* this should be impossible (single select) */ 786 GNUNET_break (0); 787 use_reply_with_error (uc, 788 MHD_HTTP_INTERNAL_SERVER_ERROR, 789 TALER_EC_GENERIC_DB_FETCH_FAILED, 790 "get_template"); 791 return; 792 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 793 /* template not found! */ 794 use_reply_with_error (uc, 795 MHD_HTTP_NOT_FOUND, 796 TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN, 797 template_id); 798 return; 799 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 800 /* all good */ 801 break; 802 } 803 if (uc->template_type != 804 TALER_MERCHANT_template_type_from_contract ( 805 uc->lookup_template.etp.template_contract)) 806 { 807 GNUNET_break_op (0); 808 use_reply_with_error ( 809 uc, 810 MHD_HTTP_CONFLICT, 811 TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_TYPE, 812 "template_contract has different type"); 813 return; 814 } 815 uc->phase++; 816 } 817 818 819 /* ***************** USE_PHASE_PARSE_TEMPLATE **************** */ 820 821 822 /** 823 * Parse template. 824 * 825 * @param[in,out] uc use context 826 */ 827 static void 828 handle_phase_template_contract (struct UseContext *uc) 829 { 830 const char *err_name; 831 enum GNUNET_GenericReturnValue res; 832 833 res = TALER_MERCHANT_template_contract_parse ( 834 uc->lookup_template.etp.template_contract, 835 &uc->template_contract, 836 &err_name); 837 if (GNUNET_OK != res) 838 { 839 GNUNET_break (0); 840 use_reply_with_error (uc, 841 MHD_HTTP_INTERNAL_SERVER_ERROR, 842 TALER_EC_GENERIC_DB_FETCH_FAILED, 843 err_name); 844 return; 845 } 846 uc->phase++; 847 } 848 849 850 /* ***************** USE_PHASE_DB_FETCH **************** */ 851 852 /** 853 * Fetch DB data for inventory templates. 854 * 855 * @param[in,out] uc use context 856 */ 857 static void 858 handle_phase_db_fetch (struct UseContext *uc) 859 { 860 struct TMH_MerchantInstance *mi = uc->hc->instance; 861 862 switch (uc->template_type) 863 { 864 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 865 uc->phase++; 866 return; 867 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 868 uc->phase++; 869 return; 870 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 871 break; 872 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 873 GNUNET_assert (0); 874 } 875 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 enum GNUNET_DB_QueryStatus qs; 883 884 qs = TALER_MERCHANTDB_get_product (TMH_db, 885 mi->settings.id, 886 item->product_id, 887 &item->pd, 888 &item->num_categories, 889 &item->categories); 890 switch (qs) 891 { 892 case GNUNET_DB_STATUS_HARD_ERROR: 893 GNUNET_break (0); 894 use_reply_with_error (uc, 895 MHD_HTTP_INTERNAL_SERVER_ERROR, 896 TALER_EC_GENERIC_DB_FETCH_FAILED, 897 "get_product"); 898 return; 899 case GNUNET_DB_STATUS_SOFT_ERROR: 900 GNUNET_break (0); 901 use_reply_with_error (uc, 902 MHD_HTTP_INTERNAL_SERVER_ERROR, 903 TALER_EC_GENERIC_DB_FETCH_FAILED, 904 "get_product"); 905 return; 906 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 907 use_reply_with_error (uc, 908 MHD_HTTP_NOT_FOUND, 909 TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN, 910 item->product_id); 911 return; 912 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 913 break; 914 } 915 } 916 uc->phase++; 917 } 918 919 920 /* *************** Helpers for USE_PHASE_VERIFY ***************** */ 921 922 /** 923 * Check that @a amount is within the limits (if any) the template 924 * imposes on amounts the client had an influence on. Amounts the 925 * merchant hard-coded in the template are not subject to these limits. 926 * Replies with an error if the check fails. 927 * 928 * @param[in,out] uc use context 929 * @param amount amount to check, excluding any tip 930 * @param detail hint to return to the client on failure 931 * @return #GNUNET_OK if @a amount is acceptable 932 */ 933 static enum GNUNET_GenericReturnValue 934 check_amount_limits (struct UseContext *uc, 935 const struct TALER_Amount *amount, 936 const char *detail) 937 { 938 const struct TALER_MERCHANT_TemplateContract *tc = &uc->template_contract; 939 const struct TALER_Amount *limit; 940 char *msg; 941 942 if (tc->no_min_amount && 943 tc->no_max_amount) 944 return GNUNET_OK; 945 /* Parsing the template guarantees both limits to be in the same 946 currency, so it suffices to check @a amount against either one. 947 We must check against a limit (and not merely against the 948 currency of the template) as it is the limits that @a amount is 949 compared to below, and comparing amounts of different currencies 950 fails an assertion. */ 951 limit = tc->no_min_amount 952 ? &tc->max_amount 953 : &tc->min_amount; 954 if (GNUNET_YES != 955 TALER_amount_cmp_currency (amount, 956 limit)) 957 { 958 GNUNET_break_op (0); 959 use_reply_with_error (uc, 960 MHD_HTTP_CONFLICT, 961 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 962 limit->currency); 963 return GNUNET_SYSERR; 964 } 965 if ( (! tc->no_min_amount) && 966 (0 > TALER_amount_cmp (amount, 967 &tc->min_amount)) ) 968 { 969 GNUNET_break_op (0); 970 GNUNET_asprintf (&msg, 971 "%s is below the min_amount of %s", 972 detail, 973 TALER_amount2s (&tc->min_amount)); 974 } 975 else if ( (! tc->no_max_amount) && 976 (0 < TALER_amount_cmp (amount, 977 &tc->max_amount)) ) 978 { 979 GNUNET_break_op (0); 980 GNUNET_asprintf (&msg, 981 "%s is above the max_amount of %s", 982 detail, 983 TALER_amount2s (&tc->max_amount)); 984 } 985 else 986 { 987 return GNUNET_OK; 988 } 989 use_reply_with_error ( 990 uc, 991 MHD_HTTP_CONFLICT, 992 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 993 msg); 994 GNUNET_free (msg); 995 return GNUNET_SYSERR; 996 } 997 998 999 /** 1000 * Check if the given product ID appears in the array of allowed_products. 1001 * 1002 * @param allowed_products JSON array of product IDs allowed by the template, may be NULL 1003 * @param product_id product ID to check 1004 * @return true if the product ID is in the list 1005 */ 1006 static bool 1007 product_id_allowed (const json_t *allowed_products, 1008 const char *product_id) 1009 { 1010 const json_t *entry; 1011 size_t idx; 1012 1013 if (NULL == allowed_products) 1014 return false; 1015 json_array_foreach ((json_t *) allowed_products, idx, entry) 1016 { 1017 if (! json_is_string (entry)) 1018 { 1019 GNUNET_break (0); 1020 continue; 1021 } 1022 if (0 == strcmp (json_string_value (entry), 1023 product_id)) 1024 return true; 1025 } 1026 return false; 1027 } 1028 1029 1030 /** 1031 * Check if any product category is in the selected_categories list. 1032 * 1033 * @param allowed_categories JSON array of categories allowed by the template, may be NULL 1034 * @param num_categories length of @a categories 1035 * @param categories list of categories of the selected product 1036 * @return true if any category of the product is in the list of allowed categories matches 1037 */ 1038 static bool 1039 category_allowed (const json_t *allowed_categories, 1040 size_t num_categories, 1041 const uint64_t categories[num_categories]) 1042 { 1043 const json_t *entry; 1044 size_t idx; 1045 1046 if (NULL == allowed_categories) 1047 return false; 1048 json_array_foreach ((json_t *) allowed_categories, 1049 idx, 1050 entry) 1051 { 1052 uint64_t selected_id; 1053 1054 if (! json_is_integer (entry)) 1055 { 1056 GNUNET_break (0); 1057 continue; 1058 } 1059 if (0 > json_integer_value (entry)) 1060 { 1061 GNUNET_break (0); 1062 continue; 1063 } 1064 selected_id = (uint64_t) json_integer_value (entry); 1065 for (size_t i = 0; i < num_categories; i++) 1066 { 1067 if (categories[i] == selected_id) 1068 return true; 1069 } 1070 } 1071 return false; 1072 } 1073 1074 1075 /** 1076 * Verify request data for inventory templates. 1077 * Checks that the selected products are allowed 1078 * for this template. 1079 * 1080 * @param[in,out] uc use context 1081 * @return #GNUNET_OK on success 1082 */ 1083 static enum GNUNET_GenericReturnValue 1084 verify_using_templates_inventory (struct UseContext *uc) 1085 { 1086 if (uc->template_contract.details.inventory.choose_one && 1087 (1 != uc->parse_request.inventory.items_len)) 1088 { 1089 GNUNET_break_op (0); 1090 use_reply_with_error (uc, 1091 MHD_HTTP_CONFLICT, 1092 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1093 "inventory_selection"); 1094 return GNUNET_SYSERR; 1095 } 1096 if (uc->template_contract.details.inventory.selected_all) 1097 return GNUNET_OK; 1098 for (unsigned int i = 0; 1099 i < uc->parse_request.inventory.items_len; 1100 i++) 1101 { 1102 struct InventoryTemplateItemContext *item = 1103 &uc->parse_request.inventory.items[i]; 1104 const char *eparam = NULL; 1105 1106 if (GNUNET_OK != 1107 TALER_MERCHANT_vk_process_quantity_inputs ( 1108 TALER_MERCHANT_VK_QUANTITY, 1109 item->pd.allow_fractional_quantity, 1110 true, 1111 0, 1112 false, 1113 item->unit_quantity, 1114 &item->quantity_value, 1115 &item->quantity_frac, 1116 &eparam)) 1117 { 1118 GNUNET_break_op (0); 1119 use_reply_with_error (uc, 1120 MHD_HTTP_BAD_REQUEST, 1121 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1122 eparam); 1123 return GNUNET_SYSERR; 1124 } 1125 1126 if (0 == item->pd.price_array_length) 1127 { 1128 GNUNET_break (0); 1129 use_reply_with_error (uc, 1130 MHD_HTTP_INTERNAL_SERVER_ERROR, 1131 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 1132 "price_array"); 1133 return GNUNET_SYSERR; 1134 } 1135 1136 /* The line-total computation multiplies the unit price by the 1137 integer quantity using TALER_amount_multiply(), whose factor 1138 argument is only a uint32_t. A quantity that does not fit into 1139 32 bits would be silently truncated by the (uint32_t) cast in 1140 compute_line_total() (e.g. quantity == 2^32 truncates to 0), 1141 producing a wrong -- and attacker-controllable, much too low -- 1142 price. Reject such quantities rather than truncate them. */ 1143 if (item->quantity_value > UINT32_MAX) 1144 { 1145 GNUNET_break_op (0); 1146 use_reply_with_error (uc, 1147 MHD_HTTP_BAD_REQUEST, 1148 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1149 "unit_quantity"); 1150 return GNUNET_SYSERR; 1151 } 1152 } 1153 1154 for (unsigned int i = 0; 1155 i < uc->parse_request.inventory.items_len; 1156 i++) 1157 { 1158 struct InventoryTemplateItemContext *item = 1159 &uc->parse_request.inventory.items[i]; 1160 1161 if (product_id_allowed (uc->template_contract.details.inventory. 1162 selected_products, 1163 item->product_id)) 1164 continue; 1165 if (category_allowed ( 1166 uc->template_contract.details.inventory.selected_categories, 1167 item->num_categories, 1168 item->categories)) 1169 continue; 1170 GNUNET_break_op (0); 1171 use_reply_with_error ( 1172 uc, 1173 MHD_HTTP_CONFLICT, 1174 TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_PRODUCT, 1175 item->product_id); 1176 return GNUNET_SYSERR; 1177 } 1178 return GNUNET_OK; 1179 } 1180 1181 1182 /** 1183 * Verify request data for fixed-order templates. 1184 * As here we cannot compute the total amount, either 1185 * the template or the client request must provide it. 1186 * 1187 * @param[in,out] uc use context 1188 * @return #GNUNET_OK on success 1189 */ 1190 static enum GNUNET_GenericReturnValue 1191 verify_using_templates_fixed ( 1192 struct UseContext *uc) 1193 { 1194 if ( (! uc->parse_request.no_amount) && 1195 (! uc->template_contract.no_amount) ) 1196 { 1197 GNUNET_break_op (0); 1198 use_reply_with_error (uc, 1199 MHD_HTTP_CONFLICT, 1200 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 1201 NULL); 1202 return GNUNET_SYSERR; 1203 } 1204 if (uc->parse_request.no_amount && 1205 uc->template_contract.no_amount) 1206 { 1207 GNUNET_break_op (0); 1208 use_reply_with_error (uc, 1209 MHD_HTTP_CONFLICT, 1210 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT, 1211 NULL); 1212 return GNUNET_SYSERR; 1213 } 1214 return GNUNET_OK; 1215 } 1216 1217 1218 /** 1219 * Turn a plain @e amount given by the client into an entry in the 1220 * @e choice_amounts array. This shorthand is what the generic 1221 * template flow (``editable_defaults`` and ``taler://pay-template`` 1222 * URIs) can express, and thus only works if the template has exactly 1223 * one choice with an editable amount. 1224 * 1225 * @param[in,out] uc use context 1226 * @return #GNUNET_OK on success 1227 */ 1228 static enum GNUNET_GenericReturnValue 1229 resolve_paivana_amount_shorthand ( 1230 struct UseContext *uc) 1231 { 1232 const struct TALER_MERCHANT_TemplateContractPaivana *tcp 1233 = &uc->template_contract.details.paivana; 1234 struct PaivanaChoiceAmount ca = { 1235 .amount = uc->parse_request.amount 1236 }; 1237 unsigned int editable = 0; 1238 1239 for (unsigned int i = 0; i < tcp->choices_len; i++) 1240 { 1241 if (! tcp->choices[i].editable_amount) 1242 continue; 1243 ca.choice_index = i; 1244 editable++; 1245 } 1246 if (0 == editable) 1247 { 1248 GNUNET_break_op (0); 1249 use_reply_with_error ( 1250 uc, 1251 MHD_HTTP_CONFLICT, 1252 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 1253 "template does not allow the amount to be edited"); 1254 return GNUNET_SYSERR; 1255 } 1256 if (1 != editable) 1257 { 1258 GNUNET_break_op (0); 1259 use_reply_with_error ( 1260 uc, 1261 MHD_HTTP_CONFLICT, 1262 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 1263 "template has more than one editable choice, use choice_amounts"); 1264 return GNUNET_SYSERR; 1265 } 1266 GNUNET_array_append (uc->parse_request.paivana.choice_amounts, 1267 uc->parse_request.paivana.choice_amounts_len, 1268 ca); 1269 return GNUNET_OK; 1270 } 1271 1272 1273 /** 1274 * Verify request data for paivana templates. 1275 * 1276 * @param[in,out] uc use context 1277 * @return #GNUNET_OK on success 1278 */ 1279 static enum GNUNET_GenericReturnValue 1280 verify_using_templates_paivana ( 1281 struct UseContext *uc) 1282 { 1283 const struct TALER_MERCHANT_TemplateContractPaivana *tcp 1284 = &uc->template_contract.details.paivana; 1285 1286 if ( (! uc->parse_request.no_amount) && 1287 (GNUNET_OK != 1288 resolve_paivana_amount_shorthand (uc)) ) 1289 return GNUNET_SYSERR; 1290 for (unsigned int i = 0; 1291 i < uc->parse_request.paivana.choice_amounts_len; 1292 i++) 1293 { 1294 const struct PaivanaChoiceAmount *ca 1295 = &uc->parse_request.paivana.choice_amounts[i]; 1296 const struct TALER_MERCHANT_OrderChoice *choice; 1297 1298 if (ca->choice_index >= tcp->choices_len) 1299 { 1300 GNUNET_break_op (0); 1301 use_reply_with_error (uc, 1302 MHD_HTTP_BAD_REQUEST, 1303 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1304 "choice_amounts::choice_index out of range"); 1305 return GNUNET_SYSERR; 1306 } 1307 choice = &tcp->choices[ca->choice_index]; 1308 if (! choice->editable_amount) 1309 { 1310 GNUNET_break_op (0); 1311 use_reply_with_error ( 1312 uc, 1313 MHD_HTTP_CONFLICT, 1314 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 1315 "selected choice has a fixed amount"); 1316 return GNUNET_SYSERR; 1317 } 1318 /* The currency is baked into the choice (say via 'max_fee'), 1319 so the client may only change the value, not the currency. */ 1320 if (GNUNET_YES != 1321 TALER_amount_cmp_currency (&ca->amount, 1322 &choice->amount)) 1323 { 1324 GNUNET_break_op (0); 1325 use_reply_with_error (uc, 1326 MHD_HTTP_CONFLICT, 1327 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1328 choice->amount.currency); 1329 return GNUNET_SYSERR; 1330 } 1331 if (GNUNET_OK != 1332 check_amount_limits (uc, 1333 &ca->amount, 1334 "amount of the selected choice")) 1335 return GNUNET_SYSERR; 1336 } 1337 if (NULL != uc->template_contract.details.paivana.website_regex) 1338 { 1339 regex_t ex; 1340 bool allowed = false; 1341 1342 if (0 != regcomp (&ex, 1343 uc->template_contract.details.paivana.website_regex, 1344 REG_NOSUB | REG_EXTENDED)) 1345 { 1346 GNUNET_break_op (0); 1347 return GNUNET_SYSERR; 1348 } 1349 if (0 == 1350 regexec (&ex, 1351 uc->parse_request.paivana.website, 1352 0, NULL, 1353 0)) 1354 { 1355 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1356 "Website `%s' allowed by template\n", 1357 uc->parse_request.paivana.website); 1358 allowed = true; 1359 } 1360 regfree (&ex); 1361 if (! allowed) 1362 { 1363 GNUNET_break_op (0); 1364 use_reply_with_error (uc, 1365 MHD_HTTP_BAD_REQUEST, 1366 TALER_EC_GENERIC_PARAMETER_MALFORMED, 1367 "website_regex"); 1368 return GNUNET_SYSERR; 1369 } 1370 } 1371 return GNUNET_OK; 1372 } 1373 1374 1375 /** 1376 * Verify that the client request is structurally acceptable for the specified 1377 * template. Does NOT check the total amount being reasonable. 1378 * 1379 * @param[in,out] uc use context 1380 */ 1381 static void 1382 handle_phase_verify ( 1383 struct UseContext *uc) 1384 { 1385 enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; 1386 1387 if ( (NULL != uc->parse_request.summary) && 1388 (NULL != uc->template_contract.summary) ) 1389 { 1390 GNUNET_break_op (0); 1391 use_reply_with_error (uc, 1392 MHD_HTTP_CONFLICT, 1393 TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT, 1394 NULL); 1395 return; 1396 } 1397 if ( (NULL == uc->parse_request.summary) && 1398 (NULL == uc->template_contract.summary) ) 1399 { 1400 GNUNET_break_op (0); 1401 use_reply_with_error (uc, 1402 MHD_HTTP_CONFLICT, 1403 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY, 1404 NULL); 1405 return; 1406 } 1407 if ( (! uc->parse_request.no_amount) && 1408 (NULL != uc->template_contract.currency) && 1409 (0 != strcasecmp (uc->template_contract.currency, 1410 uc->parse_request.amount.currency)) ) 1411 { 1412 GNUNET_break_op (0); 1413 use_reply_with_error (uc, 1414 MHD_HTTP_CONFLICT, 1415 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1416 uc->template_contract.currency); 1417 return; 1418 } 1419 switch (uc->template_type) 1420 { 1421 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1422 res = verify_using_templates_fixed (uc); 1423 break; 1424 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1425 res = verify_using_templates_paivana (uc); 1426 break; 1427 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 1428 res = verify_using_templates_inventory (uc); 1429 break; 1430 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1431 GNUNET_assert (0); 1432 } 1433 if (GNUNET_OK == res) 1434 uc->phase++; 1435 } 1436 1437 1438 /* ***************** USE_PHASE_COMPUTE_PRICE **************** */ 1439 1440 1441 /** 1442 * Find the amount the client picked for the choice at @a choice_index 1443 * of a paivana template. 1444 * 1445 * @param uc use context 1446 * @param choice_index index into the choices of the template contract 1447 * @return NULL if the client did not pick an amount for this choice, 1448 * in which case the amount from the template applies 1449 */ 1450 static const struct TALER_Amount * 1451 find_paivana_choice_amount (const struct UseContext *uc, 1452 unsigned int choice_index) 1453 { 1454 for (unsigned int i = 0; 1455 i < uc->parse_request.paivana.choice_amounts_len; 1456 i++) 1457 { 1458 const struct PaivanaChoiceAmount *ca 1459 = &uc->parse_request.paivana.choice_amounts[i]; 1460 1461 if (choice_index == ca->choice_index) 1462 return &ca->amount; 1463 } 1464 return NULL; 1465 } 1466 1467 1468 /** 1469 * Compute the line total for a product based on quantity. 1470 * 1471 * @param unit_price price per unit 1472 * @param quantity integer quantity 1473 * @param quantity_frac fractional quantity (0..TALER_MERCHANT_UNIT_FRAC_BASE-1) 1474 * @param[out] line_total resulting line total 1475 * @return #GNUNET_OK on success 1476 */ 1477 static enum GNUNET_GenericReturnValue 1478 compute_line_total (const struct TALER_Amount *unit_price, 1479 uint64_t quantity, 1480 uint32_t quantity_frac, 1481 struct TALER_Amount *line_total) 1482 { 1483 struct TALER_Amount tmp; 1484 1485 GNUNET_assert (GNUNET_OK == 1486 TALER_amount_set_zero (unit_price->currency, 1487 line_total)); 1488 if ( (0 != quantity) && 1489 (0 > 1490 TALER_amount_multiply (line_total, 1491 unit_price, 1492 (uint32_t) quantity)) ) 1493 { 1494 GNUNET_break (0); 1495 return GNUNET_SYSERR; 1496 } 1497 if (0 == quantity_frac) 1498 return GNUNET_OK; 1499 if (0 > 1500 TALER_amount_multiply (&tmp, 1501 unit_price, 1502 quantity_frac)) 1503 { 1504 GNUNET_break (0); 1505 return GNUNET_SYSERR; 1506 } 1507 TALER_amount_divide (&tmp, 1508 &tmp, 1509 TALER_MERCHANT_UNIT_FRAC_BASE); 1510 if (0 > 1511 TALER_amount_add (line_total, 1512 line_total, 1513 &tmp)) 1514 { 1515 GNUNET_break (0); 1516 return GNUNET_SYSERR; 1517 } 1518 return GNUNET_OK; 1519 } 1520 1521 1522 /** 1523 * Find the price of the given @a item in the specified 1524 * @a currency. 1525 * 1526 * @param currency currency to search price in 1527 * @param item item to check prices of 1528 * @return NULL if a suitable price was not found 1529 */ 1530 static const struct TALER_Amount * 1531 find_item_price_in_currency ( 1532 const char *currency, 1533 const struct InventoryTemplateItemContext *item) 1534 { 1535 for (size_t j = 0; j < item->pd.price_array_length; j++) 1536 { 1537 if (0 == strcasecmp (item->pd.price_array[j].currency, 1538 currency)) 1539 return &item->pd.price_array[j]; 1540 } 1541 return NULL; 1542 } 1543 1544 1545 /** 1546 * Compute totals for all currencies shared across selected products. 1547 * 1548 * @param[in,out] uc use context 1549 * @return #GNUNET_OK on success (including no price due to no items) 1550 * #GNUNET_NO if we could not find a price in any accepted currency 1551 * for all selected products 1552 * #GNUNET_SYSERR on arithmetic issues (internal error) 1553 */ 1554 static enum GNUNET_GenericReturnValue 1555 compute_totals_per_currency (struct UseContext *uc) 1556 { 1557 const struct InventoryTemplateItemContext *items 1558 = uc->parse_request.inventory.items; 1559 unsigned int items_len = uc->parse_request.inventory.items_len; 1560 1561 if (0 == items_len) 1562 return GNUNET_NO; 1563 for (size_t i = 0; i < items[0].pd.price_array_length; i++) 1564 { 1565 const struct TALER_Amount *price 1566 = &items[0].pd.price_array[i]; 1567 struct TALER_Amount zero; 1568 1569 if (! TMH_test_exchange_configured_for_currency (price->currency)) 1570 continue; 1571 GNUNET_assert (GNUNET_OK == 1572 TALER_amount_set_zero (price->currency, 1573 &zero)); 1574 GNUNET_array_append (uc->compute_price.totals, 1575 uc->compute_price.totals_len, 1576 zero); 1577 } 1578 if (0 == uc->compute_price.totals_len) 1579 { 1580 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1581 "No currency supported by our configuration in which we have prices for first selected product!\n"); 1582 return GNUNET_NO; 1583 } 1584 /* Loop through items, ensure each currency exists and sum totals. */ 1585 for (unsigned int i = 0; i < items_len; i++) 1586 { 1587 const struct InventoryTemplateItemContext *item = &items[i]; 1588 unsigned int c = 0; 1589 1590 while (c < uc->compute_price.totals_len) 1591 { 1592 struct TALER_Amount *total = &uc->compute_price.totals[c]; 1593 const struct TALER_Amount *unit_price; 1594 struct TALER_Amount line_total; 1595 1596 unit_price = find_item_price_in_currency (total->currency, 1597 item); 1598 if (NULL == unit_price) 1599 { 1600 /* Drop the currency: we have no price in one of 1601 the selected products */ 1602 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1603 "Product `%s' has no price in %s: dropping currency\n", 1604 item->product_id, 1605 total->currency); 1606 *total = uc->compute_price.totals[--uc->compute_price.totals_len]; 1607 continue; 1608 } 1609 if (GNUNET_OK != 1610 compute_line_total (unit_price, 1611 item->quantity_value, 1612 item->quantity_frac, 1613 &line_total)) 1614 { 1615 GNUNET_break (0); 1616 return GNUNET_SYSERR; 1617 } 1618 if (0 > 1619 TALER_amount_add (total, 1620 total, 1621 &line_total)) 1622 { 1623 GNUNET_break (0); 1624 return GNUNET_SYSERR; 1625 } 1626 c++; 1627 } 1628 } 1629 if (0 == uc->compute_price.totals_len) 1630 { 1631 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1632 "No currency available in which we have prices for all selected products!\n"); 1633 GNUNET_free (uc->compute_price.totals); 1634 } 1635 return (0 == uc->compute_price.totals_len) 1636 ? GNUNET_NO 1637 : GNUNET_OK; 1638 } 1639 1640 1641 /** 1642 * Compute total for only the given @a currency. 1643 * 1644 * @param items_len length of @a items 1645 * @param items inventory items 1646 * @param currency currency to total 1647 * @param[out] total computed total 1648 * @return #GNUNET_OK on success 1649 * #GNUNET_NO if we could not find a price in any accepted currency 1650 * for all selected products 1651 * #GNUNET_SYSERR on arithmetic issues (internal error) 1652 */ 1653 static enum GNUNET_GenericReturnValue 1654 compute_inventory_total (unsigned int items_len, 1655 const struct InventoryTemplateItemContext *items, 1656 const char *currency, 1657 struct TALER_Amount *total) 1658 { 1659 GNUNET_assert (NULL != currency); 1660 GNUNET_assert (GNUNET_OK == 1661 TALER_amount_set_zero (currency, 1662 total)); 1663 for (unsigned int i = 0; i < items_len; i++) 1664 { 1665 const struct InventoryTemplateItemContext *item = &items[i]; 1666 const struct TALER_Amount *unit_price; 1667 struct TALER_Amount line_total; 1668 1669 unit_price = find_item_price_in_currency (currency, 1670 item); 1671 if (NULL == unit_price) 1672 { 1673 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1674 "compute_inventory_total: no price in %s for product `%s'\n", 1675 currency, 1676 item->product_id); 1677 return GNUNET_NO; 1678 } 1679 if (GNUNET_OK != 1680 compute_line_total (unit_price, 1681 item->quantity_value, 1682 item->quantity_frac, 1683 &line_total)) 1684 { 1685 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1686 "compute_inventory_total: line total failed for %s in %s\n", 1687 item->product_id, 1688 currency); 1689 return GNUNET_SYSERR; 1690 } 1691 if (0 > 1692 TALER_amount_add (total, 1693 total, 1694 &line_total)) 1695 { 1696 GNUNET_break (0); 1697 return GNUNET_SYSERR; 1698 } 1699 } 1700 return GNUNET_OK; 1701 } 1702 1703 1704 /** 1705 * Compute total price. 1706 * 1707 * @param[in,out] uc use context 1708 */ 1709 static void 1710 handle_phase_compute_price (struct UseContext *uc) 1711 { 1712 const char *primary_currency; 1713 enum GNUNET_GenericReturnValue ret; 1714 1715 switch (uc->template_type) 1716 { 1717 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 1718 uc->compute_price.totals 1719 = GNUNET_new (struct TALER_Amount); 1720 uc->compute_price.totals_len 1721 = 1; 1722 if (uc->parse_request.no_amount) 1723 { 1724 GNUNET_assert (! uc->template_contract.no_amount); 1725 *uc->compute_price.totals 1726 = uc->template_contract.amount; 1727 } 1728 else 1729 { 1730 GNUNET_assert (uc->template_contract.no_amount); 1731 *uc->compute_price.totals 1732 = uc->parse_request.amount; 1733 if (! uc->parse_request.no_tip) 1734 { 1735 /* Per the API specification, the client's 'amount' is "the 1736 amount to be paid, including tip", while the total we compute 1737 in this phase excludes the tip: #handle_phase_check_tip() 1738 adds it back on top. Without removing it here, the total 1739 would end up being 'amount' + 'tip' and could thus never 1740 match the 'amount' that #handle_phase_check_total() compares 1741 it against. */ 1742 if (GNUNET_YES != 1743 TALER_amount_cmp_currency (&uc->parse_request.tip, 1744 uc->compute_price.totals)) 1745 { 1746 GNUNET_break_op (0); 1747 use_reply_with_error (uc, 1748 MHD_HTTP_CONFLICT, 1749 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1750 uc->parse_request.tip.currency); 1751 return; 1752 } 1753 if (0 > 1754 TALER_amount_subtract (uc->compute_price.totals, 1755 uc->compute_price.totals, 1756 &uc->parse_request.tip)) 1757 { 1758 GNUNET_break_op (0); 1759 use_reply_with_error ( 1760 uc, 1761 MHD_HTTP_CONFLICT, 1762 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 1763 "tip exceeds amount"); 1764 return; 1765 } 1766 } 1767 /* Only an amount the client chose is subject to the limits. */ 1768 if (GNUNET_OK != 1769 check_amount_limits (uc, 1770 uc->compute_price.totals, 1771 "amount")) 1772 return; 1773 } 1774 uc->phase++; 1775 return; 1776 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 1777 /* handled below */ 1778 break; 1779 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 1780 { 1781 const struct TALER_MERCHANT_TemplateContractPaivana *tcp 1782 = &uc->template_contract.details.paivana; 1783 json_t *choices; 1784 1785 choices = json_array (); 1786 GNUNET_assert (NULL != choices); 1787 for (size_t i = 0; i < tcp->choices_len; i++) 1788 { 1789 /* Make deep copy, we're going to MODIFY it! */ 1790 struct TALER_MERCHANT_OrderChoice choice 1791 = tcp->choices[i]; 1792 const struct TALER_Amount *ca; 1793 1794 ca = find_paivana_choice_amount (uc, 1795 i); 1796 if (NULL != ca) 1797 choice.amount = *ca; 1798 choice.no_tip = uc->parse_request.no_tip; 1799 if (! uc->parse_request.no_tip) 1800 { 1801 if (GNUNET_YES != 1802 TALER_amount_cmp_currency (&choice.amount, 1803 &uc->parse_request.tip)) 1804 continue; /* tip does not match choice currency */ 1805 choice.tip = uc->parse_request.tip; 1806 if (0 > 1807 TALER_amount_add (&choice.amount, 1808 &choice.amount, 1809 &uc->parse_request.tip)) 1810 { 1811 GNUNET_break (0); 1812 use_reply_with_error (uc, 1813 MHD_HTTP_INTERNAL_SERVER_ERROR, 1814 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1815 "tip"); 1816 return; 1817 } 1818 } 1819 GNUNET_assert (0 == 1820 json_array_append_new ( 1821 choices, 1822 TALER_MERCHANT_json_from_order_choice (&choice))); 1823 } 1824 if (0 == json_array_size (choices)) 1825 { 1826 GNUNET_break_op (0); 1827 json_decref (choices); 1828 use_reply_with_error (uc, 1829 MHD_HTTP_CONFLICT, 1830 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY, 1831 "tip"); 1832 return; 1833 } 1834 uc->compute_price.choices = choices; 1835 } 1836 /* Note: we already did the tip and pricing 1837 fully here, so we skip these phases. */ 1838 uc->phase = USE_PHASE_CREATE_ORDER; 1839 return; 1840 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 1841 GNUNET_assert (0); 1842 } 1843 primary_currency = uc->template_contract.currency; 1844 if (! uc->parse_request.no_tip) 1845 primary_currency = uc->parse_request.tip.currency; 1846 /* An 'amount' given by the client takes precedence over the currency 1847 of the 'tip': the total we compute here is later compared against 1848 that 'amount' in #handle_phase_check_total(), which requires both 1849 to be in the same currency. If the tip then uses a different 1850 currency, #handle_phase_check_tip() rejects the request with a 1851 proper error instead. */ 1852 if (! uc->parse_request.no_amount) 1853 primary_currency = uc->parse_request.amount.currency; 1854 if (NULL == primary_currency) 1855 { 1856 ret = compute_totals_per_currency (uc); 1857 } 1858 else 1859 { 1860 uc->compute_price.totals 1861 = GNUNET_new (struct TALER_Amount); 1862 uc->compute_price.totals_len 1863 = 1; 1864 ret = compute_inventory_total ( 1865 uc->parse_request.inventory.items_len, 1866 uc->parse_request.inventory.items, 1867 primary_currency, 1868 uc->compute_price.totals); 1869 } 1870 if (GNUNET_SYSERR == ret) 1871 { 1872 use_reply_with_error ( 1873 uc, 1874 MHD_HTTP_INTERNAL_SERVER_ERROR, 1875 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1876 "calculation of currency totals failed"); 1877 return; 1878 } 1879 if (GNUNET_NO == ret) 1880 { 1881 use_reply_with_error (uc, 1882 MHD_HTTP_CONFLICT, 1883 TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY, 1884 NULL); 1885 return; 1886 } 1887 /* The client picked the products and quantities, so the 1888 resulting total is subject to the limits. */ 1889 for (unsigned int i = 0; 1890 i < uc->compute_price.totals_len; 1891 i++) 1892 { 1893 if (GNUNET_OK != 1894 check_amount_limits (uc, 1895 &uc->compute_price.totals[i], 1896 "total of the selected products")) 1897 return; 1898 } 1899 1900 uc->phase++; 1901 } 1902 1903 1904 /* ***************** USE_PHASE_CHECK_TIP **************** */ 1905 1906 1907 /** 1908 * Check that tip specified is reasonable and add to total. 1909 * 1910 * @param[in,out] uc use context 1911 */ 1912 static void 1913 handle_phase_check_tip (struct UseContext *uc) 1914 { 1915 struct TALER_Amount *total_amount; 1916 1917 if (uc->parse_request.no_tip) 1918 { 1919 uc->phase++; 1920 return; 1921 } 1922 if (0 == uc->compute_price.totals_len) 1923 { 1924 if (! TMH_test_exchange_configured_for_currency ( 1925 uc->parse_request.tip.currency)) 1926 { 1927 GNUNET_break_op (0); 1928 use_reply_with_error (uc, 1929 MHD_HTTP_CONFLICT, 1930 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1931 "Tip currency is not supported by backend"); 1932 return; 1933 } 1934 uc->compute_price.totals 1935 = GNUNET_new (struct TALER_Amount); 1936 uc->compute_price.totals_len 1937 = 1; 1938 *uc->compute_price.totals 1939 = uc->parse_request.tip; 1940 uc->phase++; 1941 return; 1942 } 1943 GNUNET_assert (1 == uc->compute_price.totals_len); 1944 total_amount = &uc->compute_price.totals[0]; 1945 if (GNUNET_YES != 1946 TALER_amount_cmp_currency (&uc->parse_request.tip, 1947 total_amount)) 1948 { 1949 GNUNET_break_op (0); 1950 use_reply_with_error (uc, 1951 MHD_HTTP_CONFLICT, 1952 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1953 uc->parse_request.tip.currency); 1954 return; 1955 } 1956 if (0 > 1957 TALER_amount_add (total_amount, 1958 total_amount, 1959 &uc->parse_request.tip)) 1960 { 1961 GNUNET_break (0); 1962 use_reply_with_error (uc, 1963 MHD_HTTP_INTERNAL_SERVER_ERROR, 1964 TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, 1965 "tip"); 1966 return; 1967 } 1968 uc->phase++; 1969 } 1970 1971 1972 /* ***************** USE_PHASE_CHECK_TOTAL **************** */ 1973 1974 /** 1975 * Check that if the client specified a total, 1976 * it matches our own calculation. 1977 * 1978 * @param[in,out] uc use context 1979 */ 1980 static void 1981 handle_phase_check_total (struct UseContext *uc) 1982 { 1983 GNUNET_assert (1 <= uc->compute_price.totals_len); 1984 if (! uc->parse_request.no_amount) 1985 { 1986 GNUNET_assert (1 == uc->compute_price.totals_len); 1987 if (GNUNET_YES != 1988 TALER_amount_cmp_currency (&uc->parse_request.amount, 1989 &uc->compute_price.totals[0])) 1990 { 1991 /* Must not be an assertion: the currency of the total we computed 1992 is influenced by the client (via 'tip' and the selected 1993 products), so a mismatch here is remotely triggerable. */ 1994 GNUNET_break_op (0); 1995 use_reply_with_error (uc, 1996 MHD_HTTP_CONFLICT, 1997 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, 1998 uc->compute_price.totals[0].currency); 1999 return; 2000 } 2001 if (0 != 2002 TALER_amount_cmp (&uc->parse_request.amount, 2003 &uc->compute_price.totals[0])) 2004 { 2005 GNUNET_break_op (0); 2006 use_reply_with_error (uc, 2007 MHD_HTTP_CONFLICT, 2008 TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, 2009 TALER_amount2s (&uc->compute_price.totals[0])); 2010 return; 2011 } 2012 } 2013 uc->phase++; 2014 } 2015 2016 2017 /* ***************** USE_PHASE_CREATE_ORDER **************** */ 2018 2019 2020 /** 2021 * Create order request for inventory templates. 2022 * 2023 * @param[in,out] uc use context 2024 */ 2025 static void 2026 create_using_templates_inventory (struct UseContext *uc) 2027 { 2028 json_t *inventory_products; 2029 json_t *choices; 2030 2031 inventory_products = json_array (); 2032 GNUNET_assert (NULL != inventory_products); 2033 for (unsigned int i = 0; 2034 i < uc->parse_request.inventory.items_len; 2035 i++) 2036 { 2037 const struct InventoryTemplateItemContext *item = 2038 &uc->parse_request.inventory.items[i]; 2039 2040 GNUNET_assert (0 == 2041 json_array_append_new ( 2042 inventory_products, 2043 GNUNET_JSON_PACK ( 2044 GNUNET_JSON_pack_string ("product_id", 2045 item->product_id), 2046 GNUNET_JSON_pack_string ("unit_quantity", 2047 item->unit_quantity)))); 2048 } 2049 choices = json_array (); 2050 GNUNET_assert (NULL != choices); 2051 for (unsigned int i = 0; 2052 i < uc->compute_price.totals_len; 2053 i++) 2054 { 2055 GNUNET_assert (0 == 2056 json_array_append_new ( 2057 choices, 2058 GNUNET_JSON_PACK ( 2059 TALER_JSON_pack_amount ("amount", 2060 &uc->compute_price.totals[i]), 2061 GNUNET_JSON_pack_allow_null ( 2062 TALER_JSON_pack_amount ("tip", 2063 uc->parse_request.no_tip 2064 ? NULL 2065 : &uc->parse_request.tip)) 2066 ))); 2067 } 2068 2069 uc->ihc.request_body 2070 = GNUNET_JSON_PACK ( 2071 GNUNET_JSON_pack_allow_null ( 2072 GNUNET_JSON_pack_string ("otp_id", 2073 uc->lookup_template.etp.otp_id)), 2074 GNUNET_JSON_pack_array_steal ("inventory_products", 2075 inventory_products), 2076 GNUNET_JSON_pack_object_steal ( 2077 "order", 2078 GNUNET_JSON_PACK ( 2079 GNUNET_JSON_pack_uint64 ("version", 2080 1), 2081 GNUNET_JSON_pack_array_steal ("choices", 2082 choices), 2083 GNUNET_JSON_pack_string ("summary", 2084 NULL == uc->parse_request.summary 2085 ? uc->template_contract.summary 2086 : uc->parse_request.summary)))); 2087 if (! GNUNET_TIME_relative_is_forever ( 2088 uc->template_contract.max_pickup_duration)) 2089 { 2090 GNUNET_assert ( 2091 0 == 2092 json_object_set_new ( 2093 uc->ihc.request_body, 2094 "max_pickup_time", 2095 GNUNET_JSON_from_timestamp ( 2096 GNUNET_TIME_absolute_to_timestamp ( 2097 GNUNET_TIME_relative_to_absolute ( 2098 uc->template_contract.max_pickup_duration))))); 2099 } 2100 } 2101 2102 2103 /** 2104 * Create order request for fixed-order templates. 2105 * 2106 * @param[in,out] uc use context 2107 */ 2108 static void 2109 create_using_templates_fixed (struct UseContext *uc) 2110 { 2111 uc->ihc.request_body 2112 = GNUNET_JSON_PACK ( 2113 GNUNET_JSON_pack_allow_null ( 2114 GNUNET_JSON_pack_string ("otp_id", 2115 uc->lookup_template.etp.otp_id)), 2116 GNUNET_JSON_pack_object_steal ( 2117 "order", 2118 GNUNET_JSON_PACK ( 2119 TALER_JSON_pack_amount ( 2120 "amount", 2121 &uc->compute_price.totals[0]), 2122 GNUNET_JSON_pack_allow_null ( 2123 TALER_JSON_pack_amount ("tip", 2124 uc->parse_request.no_tip 2125 ? NULL 2126 : &uc->parse_request.tip)), 2127 GNUNET_JSON_pack_string ( 2128 "summary", 2129 NULL == uc->parse_request.summary 2130 ? uc->template_contract.summary 2131 : uc->parse_request.summary)))); 2132 } 2133 2134 2135 /** 2136 * Create order request for paivana templates. 2137 * 2138 * @param[in,out] uc use context 2139 */ 2140 static void 2141 create_using_templates_paivana (struct UseContext *uc) 2142 { 2143 uc->ihc.request_body 2144 = GNUNET_JSON_PACK ( 2145 GNUNET_JSON_pack_string ( 2146 "session_id", 2147 uc->parse_request.paivana.paivana_id), 2148 GNUNET_JSON_pack_object_steal ( 2149 "order", 2150 GNUNET_JSON_PACK ( 2151 GNUNET_JSON_pack_uint64 ("version", 2152 1), 2153 GNUNET_JSON_pack_array_incref ("choices", 2154 uc->compute_price.choices), 2155 GNUNET_JSON_pack_string ( 2156 "summary", 2157 NULL == uc->parse_request.summary 2158 ? uc->template_contract.summary 2159 : uc->parse_request.summary), 2160 GNUNET_JSON_pack_string ("fulfillment_url", 2161 uc->parse_request.paivana.website)))); 2162 } 2163 2164 2165 static void 2166 handle_phase_create_order (struct UseContext *uc) 2167 { 2168 json_t *order; 2169 2170 GNUNET_assert (NULL == uc->ihc.request_body); 2171 switch (uc->template_type) 2172 { 2173 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 2174 create_using_templates_fixed (uc); 2175 break; 2176 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 2177 create_using_templates_paivana (uc); 2178 break; 2179 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 2180 create_using_templates_inventory (uc); 2181 break; 2182 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 2183 GNUNET_assert (0); 2184 } 2185 order = json_object_get (uc->ihc.request_body, 2186 "order"); 2187 GNUNET_assert (json_is_object (order)); 2188 /* A zero duration means that the template did not specify one. Leave the 2189 deadline absent in that case so the regular order handler applies the 2190 instance default. Older backends accepted FOREVER; also use the instance 2191 default for such legacy templates instead of generating a forbidden 2192 "never" deadline. */ 2193 if (GNUNET_TIME_relative_is_forever ( 2194 uc->template_contract.pay_duration)) 2195 { 2196 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 2197 "Template uses invalid infinite pay duration; " 2198 "using instance default\n"); 2199 } 2200 else if (! GNUNET_TIME_relative_is_zero ( 2201 uc->template_contract.pay_duration)) 2202 { 2203 GNUNET_assert ( 2204 0 == 2205 json_object_set_new ( 2206 order, 2207 "pay_deadline", 2208 GNUNET_JSON_from_timestamp ( 2209 GNUNET_TIME_relative_to_timestamp ( 2210 uc->template_contract.pay_duration)))); 2211 } 2212 uc->phase++; 2213 } 2214 2215 2216 /* ***************** Main handler **************** */ 2217 2218 enum MHD_Result 2219 TMH_post_using_templates_ID ( 2220 const struct TMH_RequestHandler *rh, 2221 struct MHD_Connection *connection, 2222 struct TMH_HandlerContext *hc) 2223 { 2224 struct UseContext *uc = hc->ctx; 2225 2226 (void) rh; 2227 if (NULL == uc) 2228 { 2229 uc = GNUNET_new (struct UseContext); 2230 uc->hc = hc; 2231 hc->ctx = uc; 2232 hc->cc = &cleanup_use_context; 2233 uc->ihc.instance = hc->instance; 2234 uc->phase = USE_PHASE_PARSE_REQUEST; 2235 uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID; 2236 } 2237 2238 while (1) 2239 { 2240 switch (uc->phase) 2241 { 2242 case USE_PHASE_PARSE_REQUEST: 2243 handle_phase_parse_request (uc); 2244 break; 2245 case USE_PHASE_LOOKUP_TEMPLATE: 2246 handle_phase_lookup_template (uc); 2247 break; 2248 case USE_PHASE_PARSE_TEMPLATE: 2249 handle_phase_template_contract (uc); 2250 break; 2251 case USE_PHASE_DB_FETCH: 2252 handle_phase_db_fetch (uc); 2253 break; 2254 case USE_PHASE_VERIFY: 2255 handle_phase_verify (uc); 2256 break; 2257 case USE_PHASE_COMPUTE_PRICE: 2258 handle_phase_compute_price (uc); 2259 break; 2260 case USE_PHASE_CHECK_TIP: 2261 handle_phase_check_tip (uc); 2262 break; 2263 case USE_PHASE_CHECK_TOTAL: 2264 handle_phase_check_total (uc); 2265 break; 2266 case USE_PHASE_CREATE_ORDER: 2267 handle_phase_create_order (uc); 2268 break; 2269 case USE_PHASE_SUBMIT_ORDER: 2270 return TMH_private_post_orders ( 2271 NULL, /* not even used */ 2272 connection, 2273 &uc->ihc); 2274 case USE_PHASE_FINISHED_MHD_YES: 2275 return MHD_YES; 2276 case USE_PHASE_FINISHED_MHD_NO: 2277 return MHD_NO; 2278 } 2279 } 2280 }