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