taler-merchant-httpd_post-templates-TEMPLATE_ID.c (64837B)
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 GNUNET_assert (NULL == uc->ihc.request_body); 2169 switch (uc->template_type) 2170 { 2171 case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: 2172 create_using_templates_fixed (uc); 2173 break; 2174 case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: 2175 create_using_templates_paivana (uc); 2176 break; 2177 case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: 2178 create_using_templates_inventory (uc); 2179 break; 2180 case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: 2181 GNUNET_assert (0); 2182 } 2183 uc->phase++; 2184 } 2185 2186 2187 /* ***************** Main handler **************** */ 2188 2189 enum MHD_Result 2190 TMH_post_using_templates_ID ( 2191 const struct TMH_RequestHandler *rh, 2192 struct MHD_Connection *connection, 2193 struct TMH_HandlerContext *hc) 2194 { 2195 struct UseContext *uc = hc->ctx; 2196 2197 (void) rh; 2198 if (NULL == uc) 2199 { 2200 uc = GNUNET_new (struct UseContext); 2201 uc->hc = hc; 2202 hc->ctx = uc; 2203 hc->cc = &cleanup_use_context; 2204 uc->ihc.instance = hc->instance; 2205 uc->phase = USE_PHASE_PARSE_REQUEST; 2206 uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID; 2207 } 2208 2209 while (1) 2210 { 2211 switch (uc->phase) 2212 { 2213 case USE_PHASE_PARSE_REQUEST: 2214 handle_phase_parse_request (uc); 2215 break; 2216 case USE_PHASE_LOOKUP_TEMPLATE: 2217 handle_phase_lookup_template (uc); 2218 break; 2219 case USE_PHASE_PARSE_TEMPLATE: 2220 handle_phase_template_contract (uc); 2221 break; 2222 case USE_PHASE_DB_FETCH: 2223 handle_phase_db_fetch (uc); 2224 break; 2225 case USE_PHASE_VERIFY: 2226 handle_phase_verify (uc); 2227 break; 2228 case USE_PHASE_COMPUTE_PRICE: 2229 handle_phase_compute_price (uc); 2230 break; 2231 case USE_PHASE_CHECK_TIP: 2232 handle_phase_check_tip (uc); 2233 break; 2234 case USE_PHASE_CHECK_TOTAL: 2235 handle_phase_check_total (uc); 2236 break; 2237 case USE_PHASE_CREATE_ORDER: 2238 handle_phase_create_order (uc); 2239 break; 2240 case USE_PHASE_SUBMIT_ORDER: 2241 return TMH_private_post_orders ( 2242 NULL, /* not even used */ 2243 connection, 2244 &uc->ihc); 2245 case USE_PHASE_FINISHED_MHD_YES: 2246 return MHD_YES; 2247 case USE_PHASE_FINISHED_MHD_NO: 2248 return MHD_NO; 2249 } 2250 } 2251 }