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