├── .github └── FUNDING.yml ├── .gitignore ├── .release.json ├── .travis.yml ├── LICENSE ├── build └── logs │ └── clover.xml ├── composer.json ├── phpunit.xml ├── readme.md ├── scrutinizer.yml ├── src ├── Cart.php ├── CartFee.php ├── CartItem.php ├── CartSubItem.php ├── Contracts │ ├── CouponContract.php │ └── LaraCartContract.php ├── Coupons │ ├── Fixed.php │ └── Percentage.php ├── Exceptions │ ├── CouponException.php │ ├── InvalidPrice.php │ ├── InvalidQuantity.php │ ├── InvalidTaxableValue.php │ └── ModelNotFound.php ├── Facades │ └── LaraCart.php ├── LaraCart.php ├── LaraCartHasher.php ├── LaraCartServiceProvider.php ├── Traits │ ├── CartOptionsMagicMethodsTrait.php │ └── CouponTrait.php ├── config │ └── laracart.php └── database │ └── migrations │ └── add_cart_session_id_to_users_table.php.stub ├── tests ├── Coupons │ └── Fixed.php ├── CouponsTest.php ├── CrossDeviceTest.php ├── FeesTest.php ├── ItemRelationTest.php ├── ItemsTest.php ├── LaraCartTest.php ├── LaraCartTestTrait.php ├── MagicFunctionsTest.php ├── Models │ ├── TestItem.php │ └── User.php ├── SubItemsTest.php └── TotalsTest.php └── upgrade-1.0-2.0.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [lukepolo] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.lock 3 | .phpunit-watcher-cache.php 4 | .phpunit.result.cache -------------------------------------------------------------------------------- /.release.json: -------------------------------------------------------------------------------- 1 | { 2 | "github": { 3 | "release": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | env: 4 | global: 5 | - XDEBUG_MODE=coverage 6 | 7 | php: 8 | - 7.2 9 | - 7.3 10 | - 7.4 11 | - 8.0 12 | 13 | addons: 14 | code_climate: 15 | repo_token: 653d80c9cb760f12e0ea14fc523f37cc07d95e69559913242810859ac65c1feb 16 | 17 | before_script: 18 | - travis_retry composer self-update 19 | - travis_retry composer install --prefer-source --no-interaction 20 | 21 | script: vendor/bin/phpunit 22 | 23 | after_script: 24 | - vendor/bin/test-reporter 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Luke Policinski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /build/logs/clover.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lukepolo/laracart", 3 | "description": "A simple cart for Laravel", 4 | "keywords": [ 5 | "laravel", 6 | "cart", 7 | "shopping", 8 | "shopping cart" 9 | ], 10 | "homepage": "https://github.com/lukepolo/laracart", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Luke Policinski", 15 | "email": "Luke@LukePOLO.com", 16 | "homepage": "http://LukePOLO.com" 17 | } 18 | ], 19 | "require": { 20 | "php": "~7.3 || ~7.4 || ~8.0", 21 | "ext-intl": "*", 22 | "illuminate/support": "~5.5.0|~5.7.0|~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0 || ^11.0 || ^12.0", 23 | "illuminate/session": "~5.5.0|~5.7.0|~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0 || ^11.0 || ^12.0", 24 | "illuminate/events": "~5.5.0|~5.7.0|~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0 || ^11.0 || ^12.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^7.5 || ^10.5 || ^11.5.3", 28 | "orchestra/testbench": "~3.5.0|~3.7.0|~3.8.0 || ^9.0 || ^10.0", 29 | "codeclimate/php-test-reporter": "dev-master", 30 | "mockery/mockery": "^1.0", 31 | "orchestra/database": "~3.5.0|~3.7.0|~3.8.0" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "LukePOLO\\LaraCart\\": "src/" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "LukePOLO\\LaraCart\\Tests\\": "tests" 41 | } 42 | }, 43 | "extra": { 44 | "laravel": { 45 | "providers": [ 46 | "LukePOLO\\LaraCart\\LaraCartServiceProvider" 47 | ], 48 | "aliases": { 49 | "LaraCart": "LukePOLO\\LaraCart\\Facades\\LaraCart" 50 | } 51 | } 52 | }, 53 | "minimum-stability": "stable" 54 | } 55 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./src/ 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | tests 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## LaraCart - Laravel Shopping Cart Package (http://laracart.lukepolo.com) 2 | 3 | [![Build Status](https://travis-ci.org/lukepolo/laracart.svg?branch=master)](https://travis-ci.org/lukepolo/laracart) [![Latest Stable Version](https://poser.pugx.org/lukepolo/laracart/v/stable)](https://packagist.org/packages/lukepolo/laracart) [![Test Coverage](https://codeclimate.com/github/lukepolo/laracart/badges/coverage.svg)](https://codeclimate.com/github/lukepolo/laracart/coverage) [![Total Downloads](https://poser.pugx.org/lukepolo/laracart/downloads)](https://packagist.org/packages/lukepolo/laracart) [![License](https://poser.pugx.org/lukepolo/laracart/license)](https://packagist.org/packages/lukepolo/laracart) 4 | 5 | ## Features 6 | 7 | - Coupons 8 | - Session Based System 9 | - Cross Device Support 10 | - Multiple cart instances 11 | - Fees such as a delivery fee 12 | - Taxation on a the item level 13 | - Prices display currency and locale 14 | - Endless item chaining for complex systems 15 | - Totals of all items within the item chains 16 | - Item Model Relation at a global and item level 17 | - Quickly insert items with your own item models 18 | 19 | ## Laravel compatibility 20 | 21 | | Laravel | laracart | 22 | | :---------------- | :--------- | 23 | | 5.1 \| 5.2 \| 5.3 | 1.1 \| 1.2 | 24 | | 5.4+ | 1.\* | 25 | 26 | ## Installation 27 | 28 | Install the package through [Composer](http://getcomposer.org/). Edit your project's `composer.json` file by adding: 29 | 30 | { 31 | "require": { 32 | ........, 33 | "lukepolo/laracart": "1.11.*" 34 | } 35 | } 36 | 37 | If using 5.4 you will need to include the service providers / facade in `app/config/app.php`: 38 | 39 | ```php 40 | LukePOLO\LaraCart\LaraCartServiceProvider::class, 41 | ``` 42 | 43 | Include the Facade : 44 | 45 | ```php 46 | 'LaraCart' => LukePOLO\LaraCart\Facades\LaraCart::class, 47 | ``` 48 | 49 | Copy over the configuration file by running the command: 50 | 51 | ``` 52 | php artisan vendor:publish --provider='LukePOLO\LaraCart\LaraCartServiceProvider' 53 | ``` 54 | 55 | ### Documentation 56 | 57 | http://laracart.lukepolo.com 58 | 59 | To Contribute to documentation use this repo : 60 | 61 | https://github.com/lukepolo/laracart-docs 62 | 63 | ## License 64 | 65 | MIT 66 | -------------------------------------------------------------------------------- /scrutinizer.yml: -------------------------------------------------------------------------------- 1 | before_commands: 2 | - 'composer install --prefer-source --no-interaction' 3 | filter: 4 | paths: 5 | - 'src/*' 6 | excluded_paths: 7 | - 'build/*' 8 | - 'vendor/*' 9 | - 'tests/*' 10 | tools: 11 | php_analyzer: true 12 | php_mess_detector: true 13 | php_code_sniffer: 14 | config: 15 | standard: PSR2 16 | php_code_coverage: 17 | config_path: phpunit.xml 18 | php_cpd: 19 | excluded_dirs: 20 | - vendor 21 | - tests 22 | php_loc: 23 | excluded_dirs: 24 | - vendor 25 | - tests 26 | php_pdepend: 27 | excluded_dirs: 28 | - vendor 29 | - tests -------------------------------------------------------------------------------- /src/Cart.php: -------------------------------------------------------------------------------- 1 | instance = $instance; 28 | $this->tax = config('laracart.tax'); 29 | $this->locale = config('laracart.locale'); 30 | $this->multipleCoupons = config('laracart.multiple_coupons'); 31 | $this->currencyCode = config('laracart.currency_code'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CartFee.php: -------------------------------------------------------------------------------- 1 | amount = floatval($amount); 31 | $this->taxable = $taxable; 32 | $this->tax = isset($options['tax']) ? $options['tax'] == 0 ? config('laracart.tax') : $options['tax'] : config('laracart.tax'); 33 | $this->options = $options; 34 | } 35 | 36 | /** 37 | * Gets the formatted amount. 38 | * 39 | * @param bool $format 40 | * @param bool $withTax 41 | * 42 | * @return string 43 | */ 44 | public function getAmount($format = true, $withTax = false) 45 | { 46 | $total = $this->amount; 47 | 48 | if ($withTax) { 49 | $total += $this->tax * $total; 50 | } 51 | 52 | return LaraCart::formatMoney($total, $this->locale, $this->currencyCode, $format); 53 | } 54 | 55 | public function getDiscount($format = true) 56 | { 57 | return LaraCart::formatMoney($this->discounted, $this->locale, $this->currencyCode, $format); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/CartItem.php: -------------------------------------------------------------------------------- 1 | 0. 48 | */ 49 | public $discounted = []; 50 | 51 | /** 52 | * CartItem constructor. 53 | * 54 | * @param $id 55 | * @param $name 56 | * @param int $qty 57 | * @param string $price 58 | * @param array $options 59 | * @param bool $taxable 60 | * @param bool|false $lineItem 61 | */ 62 | public function __construct($id, $name, $qty, $price, $options = [], $taxable = true, $lineItem = false) 63 | { 64 | $this->id = $id; 65 | $this->qty = $qty; 66 | $this->name = $name; 67 | $this->taxable = $taxable; 68 | $this->lineItem = $lineItem; 69 | $this->price = (config('laracart.prices_in_cents', false) === true ? intval($price) : floatval($price)); 70 | $this->tax = config('laracart.tax'); 71 | $this->itemModel = config('laracart.item_model', null); 72 | $this->itemModelRelations = config('laracart.item_model_relations', []); 73 | $this->excludeFromHash = config('laracart.exclude_from_hash', []); 74 | 75 | foreach ($options as $option => $value) { 76 | $this->$option = $value; 77 | } 78 | } 79 | 80 | /** 81 | * Generates a hash based on the cartItem array. 82 | * 83 | * @param bool $force 84 | * 85 | * @return string itemHash 86 | */ 87 | public function generateHash($force = false) 88 | { 89 | if ($this->lineItem === false) { 90 | $this->itemHash = null; 91 | 92 | $cartItemArray = (array) clone $this; 93 | 94 | unset($cartItemArray['discounted']); 95 | unset($cartItemArray['options']['qty']); 96 | 97 | foreach ($this->excludeFromHash as $option) { 98 | unset($cartItemArray['options'][$option]); 99 | } 100 | 101 | ksort($cartItemArray['options']); 102 | 103 | $this->itemHash = app(LaraCart::HASH)->hash($cartItemArray); 104 | } elseif ($force || empty($this->itemHash) === true) { 105 | $this->itemHash = app(LaraCart::RANHASH); 106 | } 107 | 108 | app('events')->dispatch( 109 | 'laracart.updateItem', 110 | [ 111 | 'item' => $this, 112 | 'newHash' => $this->itemHash, 113 | ] 114 | ); 115 | 116 | return $this->itemHash; 117 | } 118 | 119 | /** 120 | * Gets the hash for the item. 121 | * 122 | * @return mixed 123 | */ 124 | public function getHash() 125 | { 126 | return $this->itemHash; 127 | } 128 | 129 | /** 130 | * Search for matching options on the item. 131 | * 132 | * @return mixed 133 | */ 134 | public function find($data) 135 | { 136 | foreach ($data as $key => $value) { 137 | if ($this->$key !== $value) { 138 | return false; 139 | } 140 | } 141 | 142 | return $this; 143 | } 144 | 145 | /** 146 | * Finds a sub item by its hash. 147 | * 148 | * @param $subItemHash 149 | * 150 | * @return mixed 151 | */ 152 | public function findSubItem($subItemHash) 153 | { 154 | return Arr::get($this->subItems, $subItemHash); 155 | } 156 | 157 | /** 158 | * Adds an sub item to a item. 159 | * 160 | * @param array $subItem 161 | * 162 | * @return CartSubItem 163 | */ 164 | public function addSubItem(array $subItem) 165 | { 166 | $subItem = new CartSubItem($subItem); 167 | 168 | $this->subItems[$subItem->getHash()] = $subItem; 169 | 170 | $this->update(); 171 | 172 | return $subItem; 173 | } 174 | 175 | /** 176 | * Removes a sub item from the item. 177 | * 178 | * @param $subItemHash 179 | */ 180 | public function removeSubItem($subItemHash) 181 | { 182 | unset($this->subItems[$subItemHash]); 183 | 184 | $this->update(); 185 | } 186 | 187 | public function getPrice() 188 | { 189 | return $this->price; 190 | } 191 | 192 | /** 193 | * Gets the price of the item with or without tax, with the proper format. 194 | * 195 | * @return string 196 | */ 197 | public function total() 198 | { 199 | $total = 0; 200 | 201 | if ($this->active) { 202 | for ($qty = 0; $qty < $this->qty; $qty++) { 203 | $total += LaraCart::formatMoney($this->subTotalPerItem(false) + array_sum($this->taxSummary()[$qty]), null, null, false); 204 | } 205 | 206 | $total -= $this->getDiscount(false); 207 | 208 | if ($total < 0) { 209 | $total = 0; 210 | } 211 | } 212 | 213 | return $total; 214 | } 215 | 216 | public function taxTotal() 217 | { 218 | $total = 0; 219 | 220 | foreach ($this->taxSummary() as $itemSummary) { 221 | $total += array_sum($itemSummary); 222 | } 223 | 224 | return $total; 225 | } 226 | 227 | /** 228 | * Gets the sub total of the item based on the qty. 229 | * 230 | * @param bool $format 231 | * 232 | * @return float|string 233 | */ 234 | public function subTotal() 235 | { 236 | return $this->subTotalPerItem() * $this->qty; 237 | } 238 | 239 | public function subTotalPerItem() 240 | { 241 | $subTotal = $this->active ? ($this->price + $this->subItemsTotal()) : 0; 242 | 243 | return $subTotal; 244 | } 245 | 246 | /** 247 | * Gets the totals for the options. 248 | * 249 | * @return float 250 | */ 251 | public function subItemsTotal() 252 | { 253 | $total = 0; 254 | 255 | foreach ($this->subItems as $subItem) { 256 | $total += $subItem->subTotal(false); 257 | } 258 | 259 | return $total; 260 | } 261 | 262 | /** 263 | * Gets the discount of an item. 264 | * 265 | * @return string 266 | */ 267 | public function getDiscount() 268 | { 269 | return array_sum($this->discounted); 270 | } 271 | 272 | /** 273 | * @param CouponContract $coupon 274 | * 275 | * @return $this 276 | */ 277 | public function addCoupon(CouponContract $coupon) 278 | { 279 | $coupon->appliedToCart = false; 280 | app('laracart')->addCoupon($coupon); 281 | $this->coupon = $coupon; 282 | 283 | return $this; 284 | } 285 | 286 | public function taxSummary() 287 | { 288 | $taxed = []; 289 | // tax item by item 290 | for ($qty = 0; $qty < $this->qty; $qty++) { 291 | // keep track of what is discountable 292 | $discountable = $this->discounted[$qty] ?? 0; 293 | $price = ($this->taxable ? $this->price : 0); 294 | 295 | $taxable = $price - ($discountable > 0 ? $discountable : 0); 296 | // track what has been discounted so far 297 | $discountable = $discountable - $price; 298 | 299 | $taxed[$qty] = []; 300 | if ($taxable > 0) { 301 | if (!isset($taxed[$qty][(string) $this->tax])) { 302 | $taxed[$qty][(string) $this->tax] = 0; 303 | } 304 | $taxed[$qty][(string) $this->tax] += $taxable * $this->tax; 305 | } 306 | 307 | // tax sub item item by sub item 308 | foreach ($this->subItems as $subItem) { 309 | $subItemTaxable = 0; 310 | for ($subItemQty = 0; $subItemQty < ($subItem->qty || 1); $subItemQty++) { 311 | $subItemPrice = ($subItem->taxable ?? true) ? $subItem->price : 0; 312 | $subItemTaxable = $subItemPrice - ($discountable > 0 ? $discountable : 0); 313 | $discountable = $discountable - $subItemPrice; 314 | } 315 | 316 | if ($subItemTaxable > 0) { 317 | if (!isset($taxed[$qty][(string) $subItem->tax])) { 318 | $taxed[$qty][(string) $subItem->tax] = 0; 319 | } 320 | $taxed[$qty][(string) $subItem->tax] += $subItemTaxable * $subItem->tax; 321 | } 322 | 323 | // discount sub items ... items 324 | if (isset($subItem->items)) { 325 | foreach ($subItem->items as $item) { 326 | if ($item->taxable) { 327 | foreach ($item->taxSummary() as $itemTaxSummary) { 328 | foreach ($itemTaxSummary as $taxRate => $amount) { 329 | if (!isset($taxed[$qty][(string) $taxRate])) { 330 | $taxed[$qty][(string) $taxRate] = 0; 331 | } 332 | $taxed[$qty][(string) $taxRate] += $amount; 333 | } 334 | } 335 | } 336 | } 337 | } 338 | } 339 | } 340 | 341 | return $taxed; 342 | } 343 | 344 | /** 345 | * Sets the related model to the item. 346 | * 347 | * @param $itemModel 348 | * @param array $relations 349 | * 350 | * @throws ModelNotFound 351 | */ 352 | public function setModel($itemModel, $relations = []) 353 | { 354 | if (!class_exists($itemModel)) { 355 | throw new ModelNotFound('Could not find relation model'); 356 | } 357 | 358 | $this->itemModel = $itemModel; 359 | $this->itemModelRelations = $relations; 360 | } 361 | 362 | /** 363 | * Gets the items model class. 364 | */ 365 | public function getItemModel() 366 | { 367 | return $this->itemModel; 368 | } 369 | 370 | /** 371 | * Returns a Model. 372 | * 373 | * @throws ModelNotFound 374 | */ 375 | public function getModel() 376 | { 377 | $itemModel = (new $this->itemModel())->with($this->itemModelRelations)->find($this->id); 378 | 379 | if (empty($itemModel)) { 380 | throw new ModelNotFound('Could not find the item model for '.$this->id); 381 | } 382 | 383 | return $itemModel; 384 | } 385 | 386 | /** 387 | * A way to find sub items. 388 | * 389 | * @param $data 390 | * 391 | * @return array 392 | */ 393 | public function searchForSubItem($data) 394 | { 395 | $matches = []; 396 | 397 | foreach ($this->subItems as $subItem) { 398 | if ($subItem->find($data)) { 399 | $matches[] = $subItem; 400 | } 401 | } 402 | 403 | return $matches; 404 | } 405 | 406 | public function disable() 407 | { 408 | $this->active = false; 409 | $this->update(); 410 | } 411 | 412 | public function enable() 413 | { 414 | $this->active = true; 415 | $this->update(); 416 | } 417 | 418 | public function update() 419 | { 420 | $this->generateHash(); 421 | app('laracart')->update(); 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /src/CartSubItem.php: -------------------------------------------------------------------------------- 1 | options['items'] = []; 32 | 33 | foreach ($options as $option => $value) { 34 | Arr::set($this->options, $option, $value); 35 | } 36 | 37 | $this->qty = isset($options['qty']) ? $options['qty'] : 1; 38 | $this->taxable = isset($options['taxable']) ? $options['taxable'] : true; 39 | $this->tax = isset($options['tax']) ? $options['tax'] == 0 ? config('laracart.tax') : $options['tax'] : config('laracart.tax'); 40 | 41 | $this->itemHash = app(LaraCart::HASH)->hash($this->options); 42 | } 43 | 44 | /** 45 | * Gets the hash for the item. 46 | * 47 | * @return mixed 48 | */ 49 | public function getHash() 50 | { 51 | return $this->itemHash; 52 | } 53 | 54 | /** 55 | * Gets the formatted price. 56 | * 57 | * @return float 58 | */ 59 | public function subTotal() 60 | { 61 | $price = $this->price * $this->qty; 62 | 63 | if (isset($this->items)) { 64 | foreach ($this->items as $item) { 65 | $price += $item->subTotal(false); 66 | } 67 | } 68 | 69 | return $price; 70 | } 71 | 72 | /** 73 | * Search for matching options on the item. 74 | * 75 | * @return mixed 76 | */ 77 | public function find($data) 78 | { 79 | foreach ($data as $key => $value) { 80 | if ($this->$key === $value) { 81 | return $this; 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Contracts/CouponContract.php: -------------------------------------------------------------------------------- 1 | code = $code; 26 | $this->value = $value; 27 | 28 | $this->setOptions($options); 29 | } 30 | 31 | /** 32 | * Gets the discount amount. 33 | * 34 | * @return string 35 | */ 36 | public function discount($price) 37 | { 38 | if ($this->canApply()) { 39 | $discount = $this->value - $this->discounted; 40 | if ($discount > $price) { 41 | return $price; 42 | } 43 | 44 | return $discount; 45 | } 46 | 47 | return 0; 48 | } 49 | 50 | /** 51 | * Displays the value in a money format. 52 | * 53 | * @param null $locale 54 | * @param null $currencyCode 55 | * 56 | * @return string 57 | */ 58 | public function displayValue($locale = null, $currencyCode = null, $format = true) 59 | { 60 | return LaraCart::formatMoney( 61 | $this->value, 62 | $locale, 63 | $currencyCode, 64 | $format 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Coupons/Percentage.php: -------------------------------------------------------------------------------- 1 | code = $code; 32 | if ($value > 1) { 33 | $this->message = 'Invalid value for a percentage coupon. The value must be between 0 and 1.'; 34 | 35 | throw new CouponException($this->message); 36 | } 37 | $this->value = $value; 38 | 39 | $this->setOptions($options); 40 | } 41 | 42 | /** 43 | * Gets the discount amount. 44 | * 45 | * @return string 46 | */ 47 | public function discount($price) 48 | { 49 | if ($this->canApply()) { 50 | return $price * $this->value; 51 | } 52 | 53 | return 0; 54 | } 55 | 56 | /** 57 | * @return mixed 58 | */ 59 | public function displayValue() 60 | { 61 | return ($this->value * 100).'%'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Exceptions/CouponException.php: -------------------------------------------------------------------------------- 1 | session = $session; 43 | $this->events = $events; 44 | $this->authManager = $authManager->guard(config('laracart.guard', null)); 45 | $this->prefix = config('laracart.cache_prefix', 'laracart'); 46 | $this->itemModel = config('laracart.item_model', null); 47 | $this->itemModelRelations = config('laracart.item_model_relations', []); 48 | 49 | $this->setInstance($this->session->get($this->prefix.'.instance', 'default')); 50 | } 51 | 52 | /** 53 | * Gets all current instances inside the session. 54 | * 55 | * @return mixed 56 | */ 57 | public function getInstances() 58 | { 59 | return $this->session->get($this->prefix.'.instances', []); 60 | } 61 | 62 | /** 63 | * Sets and Gets the instance of the cart in the session we should be using. 64 | * 65 | * @param string $instance 66 | * 67 | * @return LaraCart 68 | */ 69 | public function setInstance($instance = 'default') 70 | { 71 | $this->get($instance); 72 | 73 | $this->session->put($this->prefix.'.instance', $instance); 74 | 75 | if (!in_array($instance, $this->getInstances())) { 76 | $this->session->push($this->prefix.'.instances', $instance); 77 | } 78 | $this->events->dispatch('laracart.new'); 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * Gets the instance in the session. 85 | * 86 | * @param string $instance 87 | * 88 | * @return $this cart instance 89 | */ 90 | public function get($instance = 'default') 91 | { 92 | if (config('laracart.cross_devices', false) && $this->authManager->check()) { 93 | if (!empty($cartSessionID = $this->authManager->user()->cart_session_id)) { 94 | $this->session->setId($cartSessionID); 95 | $this->session->start(); 96 | } 97 | } 98 | 99 | if (empty($this->cart = $this->session->get($this->prefix.'.'.$instance))) { 100 | $this->cart = new Cart($instance); 101 | } 102 | 103 | return $this; 104 | } 105 | 106 | /** 107 | * Gets an an attribute from the cart. 108 | * 109 | * @param $attribute 110 | * @param $defaultValue 111 | * 112 | * @return mixed 113 | */ 114 | public function getAttribute($attribute, $defaultValue = null) 115 | { 116 | return Arr::get($this->cart->attributes, $attribute, $defaultValue); 117 | } 118 | 119 | /** 120 | * Gets all the carts attributes. 121 | * 122 | * @return mixed 123 | */ 124 | public function getAttributes() 125 | { 126 | return $this->cart->attributes; 127 | } 128 | 129 | /** 130 | * Adds an Attribute to the cart. 131 | * 132 | * @param $attribute 133 | * @param $value 134 | */ 135 | public function setAttribute($attribute, $value) 136 | { 137 | Arr::set($this->cart->attributes, $attribute, $value); 138 | 139 | $this->update(); 140 | } 141 | 142 | private function updateDiscounts() 143 | { 144 | // reset discounted 145 | foreach ($this->getItems() as $item) { 146 | $item->discounted = []; 147 | } 148 | 149 | // go through each item and see if they have a coupon attached 150 | foreach ($this->getItems() as $item) { 151 | if ($item->coupon) { 152 | $item->coupon->discounted = 0; 153 | for ($qty = 0; $qty < $item->qty; $qty++) { 154 | $item->coupon->discounted += $item->discounted[$qty] = $item->coupon->discount($item->subTotalPerItem(false)); 155 | } 156 | } 157 | } 158 | 159 | // go through each coupon and apply to items that do not have a coupon attached 160 | foreach ($this->getCoupons() as $coupon) { 161 | $coupon->discounted = 0; 162 | foreach ($this->getItems() as $item) { 163 | if (!$item->coupon) { 164 | $item->discounted = []; 165 | for ($qty = 0; $qty < $item->qty; $qty++) { 166 | $coupon->discounted += $item->discounted[$qty] = $coupon->discount($item->subTotalPerItem(false)); 167 | } 168 | } 169 | } 170 | 171 | if (config('laracart.discount_fees', false)) { 172 | // go through each fee and discount 173 | foreach ($this->getFees() as $fee) { 174 | $coupon->discounted = $fee->discounted = $coupon->discount($fee->amount); 175 | } 176 | } 177 | } 178 | } 179 | 180 | /** 181 | * Updates cart session. 182 | */ 183 | public function update() 184 | { 185 | // allows us to track a discount on the item so we are able properly do taxation 186 | $this->updateDiscounts(); 187 | 188 | $this->session->put($this->prefix.'.'.$this->cart->instance, $this->cart); 189 | 190 | if (config('laracart.cross_devices', false) && $this->authManager->check()) { 191 | $this->authManager->user()->cart_session_id = $this->session->getId(); 192 | $this->authManager->user()->save(); 193 | } 194 | 195 | $this->session->reflash(); 196 | 197 | $this->session->save(); 198 | 199 | $this->events->dispatch('laracart.update', $this->cart); 200 | } 201 | 202 | /** 203 | * Removes an attribute from the cart. 204 | * 205 | * @param $attribute 206 | */ 207 | public function removeAttribute($attribute) 208 | { 209 | Arr::forget($this->cart->attributes, $attribute); 210 | 211 | $this->update(); 212 | } 213 | 214 | /** 215 | * Creates a CartItem and then adds it to cart. 216 | * 217 | * @param string|int $itemID 218 | * @param null $name 219 | * @param int $qty 220 | * @param string $price 221 | * @param array $options 222 | * @param bool|true $taxable 223 | * 224 | * @throws ModelNotFound 225 | * 226 | * @return CartItem 227 | */ 228 | public function addLine($itemID, $name = null, $qty = 1, $price = '0.00', $options = [], $taxable = true) 229 | { 230 | return $this->add($itemID, $name, $qty, $price, $options, $taxable, true); 231 | } 232 | 233 | /** 234 | * Creates a CartItem and then adds it to cart. 235 | * 236 | * @param $itemID 237 | * @param null $name 238 | * @param int $qty 239 | * @param string $price 240 | * @param array $options 241 | * @param bool|false $taxable 242 | * @param bool|false $lineItem 243 | * 244 | * @throws ModelNotFound 245 | * 246 | * @return CartItem 247 | */ 248 | public function add( 249 | $itemID, 250 | $name = null, 251 | $qty = 1, 252 | $price = '0.00', 253 | $options = [], 254 | $taxable = true, 255 | $lineItem = false 256 | ) { 257 | if (!empty(config('laracart.item_model'))) { 258 | $itemModel = $itemID; 259 | 260 | if (!$this->isItemModel($itemModel)) { 261 | $itemModel = (new $this->itemModel())->with($this->itemModelRelations)->find($itemID); 262 | } 263 | 264 | if (empty($itemModel)) { 265 | throw new ModelNotFound('Could not find the item '.$itemID); 266 | } 267 | 268 | $bindings = config('laracart.item_model_bindings'); 269 | 270 | $itemID = $itemModel->{$bindings[\LukePOLO\LaraCart\CartItem::ITEM_ID]}; 271 | 272 | if (is_int($name)) { 273 | $qty = $name; 274 | } 275 | 276 | $name = $itemModel->{$bindings[\LukePOLO\LaraCart\CartItem::ITEM_NAME]}; 277 | $price = $itemModel->{$bindings[\LukePOLO\LaraCart\CartItem::ITEM_PRICE]}; 278 | 279 | $options['model'] = $itemModel; 280 | 281 | $options = array_merge($options, $this->getItemModelOptions($itemModel, $bindings[\LukePOLO\LaraCart\CartItem::ITEM_OPTIONS])); 282 | 283 | $taxable = $itemModel->{$bindings[\LukePOLO\LaraCart\CartItem::ITEM_TAXABLE]} ? true : false; 284 | } 285 | 286 | $item = $this->addItem(new CartItem( 287 | $itemID, 288 | $name, 289 | $qty, 290 | $price, 291 | $options, 292 | $taxable, 293 | $lineItem 294 | )); 295 | 296 | $this->update(); 297 | 298 | return $this->getItem($item->getHash()); 299 | } 300 | 301 | /** 302 | * Adds the cartItem into the cart session. 303 | * 304 | * @param CartItem $cartItem 305 | * 306 | * @return CartItem 307 | */ 308 | public function addItem(CartItem $cartItem) 309 | { 310 | $itemHash = $cartItem->generateHash(); 311 | 312 | if ($this->getItem($itemHash)) { 313 | $this->getItem($itemHash)->qty += $cartItem->qty; 314 | } else { 315 | $this->cart->items[] = $cartItem; 316 | } 317 | 318 | app('events')->dispatch( 319 | 'laracart.addItem', 320 | $cartItem 321 | ); 322 | 323 | return $cartItem; 324 | } 325 | 326 | /** 327 | * Increment the quantity of a cartItem based on the itemHash. 328 | * 329 | * @param $itemHash 330 | * 331 | * @return CartItem | null 332 | */ 333 | public function increment($itemHash) 334 | { 335 | $item = $this->getItem($itemHash); 336 | $item->qty++; 337 | $this->update(); 338 | 339 | return $item; 340 | } 341 | 342 | /** 343 | * Decrement the quantity of a cartItem based on the itemHash. 344 | * 345 | * @param $itemHash 346 | * 347 | * @return CartItem | null 348 | */ 349 | public function decrement($itemHash) 350 | { 351 | $item = $this->getItem($itemHash); 352 | if ($item->qty > 1) { 353 | $item->qty--; 354 | $this->update(); 355 | 356 | return $item; 357 | } 358 | $this->removeItem($itemHash); 359 | $this->update(); 360 | } 361 | 362 | /** 363 | * Find items in the cart matching a data set. 364 | * 365 | * param $data 366 | * 367 | * @return array | CartItem | null 368 | */ 369 | public function find($data) 370 | { 371 | $matches = []; 372 | 373 | foreach ($this->getItems() as $item) { 374 | if ($item->find($data)) { 375 | $matches[] = $item; 376 | } 377 | } 378 | 379 | switch (count($matches)) { 380 | case 0: 381 | return; 382 | break; 383 | case 1: 384 | return $matches[0]; 385 | break; 386 | default: 387 | return $matches; 388 | } 389 | } 390 | 391 | /** 392 | * Finds a cartItem based on the itemHash. 393 | * 394 | * @param $itemHash 395 | * 396 | * @return CartItem | null 397 | */ 398 | public function getItem($itemHash) 399 | { 400 | return Arr::get($this->getItems(), $itemHash); 401 | } 402 | 403 | /** 404 | * Gets all the items within the cart. 405 | * 406 | * @return array 407 | */ 408 | public function getItems() 409 | { 410 | $items = []; 411 | if (isset($this->cart->items) === true) { 412 | foreach ($this->cart->items as $item) { 413 | $items[$item->getHash()] = $item; 414 | } 415 | } 416 | 417 | return $items; 418 | } 419 | 420 | /** 421 | * Updates an items attributes. 422 | * 423 | * @param $itemHash 424 | * @param $key 425 | * @param $value 426 | * 427 | * @return CartItem|null 428 | */ 429 | public function updateItem($itemHash, $key, $value) 430 | { 431 | if (empty($item = $this->getItem($itemHash)) === false) { 432 | if ($key == 'qty' && $value == 0) { 433 | return $this->removeItem($itemHash); 434 | } 435 | 436 | $item->$key = $value; 437 | } 438 | 439 | $this->update(); 440 | 441 | return $item; 442 | } 443 | 444 | /** 445 | * Removes a CartItem based on the itemHash. 446 | * 447 | * @param $itemHash 448 | */ 449 | public function removeItem($itemHash) 450 | { 451 | if (empty($this->cart->items) === false) { 452 | foreach ($this->cart->items as $itemKey => $item) { 453 | if ($item->getHash() == $itemHash) { 454 | unset($this->cart->items[$itemKey]); 455 | break; 456 | } 457 | } 458 | 459 | $this->events->dispatch('laracart.removeItem', $item); 460 | 461 | $this->update(); 462 | } 463 | } 464 | 465 | /** 466 | * Empties the carts items. 467 | */ 468 | public function emptyCart() 469 | { 470 | unset($this->cart->items); 471 | 472 | $this->update(); 473 | 474 | $this->events->dispatch('laracart.empty', $this->cart->instance); 475 | } 476 | 477 | /** 478 | * Completely destroys cart and anything associated with it. 479 | */ 480 | public function destroyCart() 481 | { 482 | $instance = $this->cart->instance; 483 | 484 | $this->session->forget($this->prefix.'.'.$instance); 485 | 486 | $this->events->dispatch('laracart.destroy', $instance); 487 | 488 | $this->cart = new Cart($instance); 489 | 490 | $this->update(); 491 | } 492 | 493 | /** 494 | * Gets the coupons for the current cart. 495 | * 496 | * @return array 497 | */ 498 | public function getCoupons() 499 | { 500 | return $this->cart->coupons; 501 | } 502 | 503 | /** 504 | * Finds a specific coupon in the cart. 505 | * 506 | * @param $code 507 | * 508 | * @return mixed 509 | */ 510 | public function findCoupon($code) 511 | { 512 | return Arr::get($this->cart->coupons, $code); 513 | } 514 | 515 | /** 516 | * Applies a coupon to the cart. 517 | * 518 | * @param CouponContract $coupon 519 | */ 520 | public function addCoupon(CouponContract $coupon) 521 | { 522 | if (!$this->cart->multipleCoupons) { 523 | $this->cart->coupons = []; 524 | } 525 | 526 | $this->cart->coupons[$coupon->code] = $coupon; 527 | 528 | $this->update(); 529 | } 530 | 531 | /** 532 | * Removes a coupon in the cart. 533 | * 534 | * @param $code 535 | */ 536 | public function removeCoupon($code) 537 | { 538 | $this->removeCouponFromItems($code); 539 | Arr::forget($this->cart->coupons, $code); 540 | $this->update(); 541 | } 542 | 543 | /** 544 | * Removes all coupons from the cart. 545 | */ 546 | public function removeCoupons() 547 | { 548 | $this->removeCouponFromItems(); 549 | $this->cart->coupons = []; 550 | $this->update(); 551 | } 552 | 553 | /** 554 | * Gets a specific fee from the fees array. 555 | * 556 | * @param $name 557 | * 558 | * @return mixed 559 | */ 560 | public function getFee($name) 561 | { 562 | return Arr::get($this->cart->fees, $name, new CartFee(null, false)); 563 | } 564 | 565 | /** 566 | * Allows to charge for additional fees that may or may not be taxable 567 | * ex - service fee , delivery fee, tips. 568 | * 569 | * @param $name 570 | * @param $amount 571 | * @param bool|false $taxable 572 | * @param array $options 573 | */ 574 | public function addFee($name, $amount, $taxable = false, array $options = []) 575 | { 576 | Arr::set($this->cart->fees, $name, new CartFee($amount, $taxable, $options)); 577 | 578 | $this->update(); 579 | } 580 | 581 | /** 582 | * Removes a fee from the fee array. 583 | * 584 | * @param $name 585 | */ 586 | public function removeFee($name) 587 | { 588 | Arr::forget($this->cart->fees, $name); 589 | 590 | $this->update(); 591 | } 592 | 593 | /** 594 | * Removes all the fees set in the cart. 595 | */ 596 | public function removeFees() 597 | { 598 | $this->cart->fees = []; 599 | 600 | $this->update(); 601 | } 602 | 603 | /** 604 | * Gets the total tax for the cart. 605 | * 606 | * @param bool|true $format 607 | * 608 | * @return string 609 | */ 610 | public function taxTotal($format = true) 611 | { 612 | $totalTax = 0; 613 | 614 | foreach ($this->getItems() as $item) { 615 | $totalTax += $item->taxTotal(false); 616 | } 617 | 618 | $totalTax += $this->feeTaxTotal(false); 619 | 620 | return $this->formatMoney($totalTax, null, null, $format); 621 | } 622 | 623 | public function feeTaxTotal($format = true) 624 | { 625 | return $this->formatMoney(array_sum($this->feeTaxSummary()), null, null, $format); 626 | } 627 | 628 | public function feeTaxSummary() 629 | { 630 | $taxed = []; 631 | if (config('laracart.fees_taxable', false)) { 632 | foreach ($this->getFees() as $fee) { 633 | if ($fee->taxable) { 634 | if (!isset($taxed[(string) $fee->tax])) { 635 | $taxed[(string) $fee->tax] = 0; 636 | } 637 | $taxed[(string) $fee->tax] += $this->formatMoney($fee->amount * $fee->tax, null, null, false); 638 | } 639 | } 640 | } 641 | 642 | return $taxed; 643 | } 644 | 645 | public function taxSummary() 646 | { 647 | $taxed = []; 648 | foreach ($this->getItems() as $item) { 649 | foreach ($item->taxSummary() as $taxRate => $amount) { 650 | if (!isset($taxed[(string) $taxRate])) { 651 | $taxed[(string) $taxRate] = 0; 652 | } 653 | $taxed[(string) $taxRate] += $amount; 654 | } 655 | } 656 | 657 | foreach ($this->feeTaxSummary() as $taxRate => $amount) { 658 | if (!isset($taxed[(string) $taxRate])) { 659 | $taxed[(string) $taxRate] = 0; 660 | } 661 | $taxed[(string) $taxRate] += $amount; 662 | } 663 | 664 | return $taxed; 665 | } 666 | 667 | /** 668 | * Gets the total of the cart with or without tax. 669 | * 670 | * @param bool $format 671 | * 672 | * @return string 673 | */ 674 | public function total($format = true) 675 | { 676 | $total = $this->itemTotals(false); 677 | $total += $this->feeSubTotal(false) + $this->feeTaxTotal(false); 678 | // $total -= $this->discountTotal(false); 679 | 680 | return $this->formatMoney($total, null, null, $format); 681 | } 682 | 683 | public function netTotal($format = true) 684 | { 685 | $total = $this->subTotal(false); 686 | $total += $this->feeSubTotal(false); 687 | $total -= $this->discountTotal(false); 688 | 689 | return $this->formatMoney($total, null, null, $format); 690 | } 691 | 692 | public function itemTotals($format = true) 693 | { 694 | $total = 0; 695 | 696 | if ($this->count() != 0) { 697 | foreach ($this->getItems() as $item) { 698 | $total += $item->total(false); 699 | } 700 | } 701 | 702 | if ($total < 0) { 703 | $total = 0; 704 | } 705 | 706 | return $this->formatMoney($total, null, null, $format); 707 | } 708 | 709 | /** 710 | * Gets the subtotal of the cart with or without tax. 711 | * 712 | * @param bool $format 713 | * 714 | * @return string 715 | */ 716 | public function subTotal($format = true) 717 | { 718 | $total = 0; 719 | 720 | if ($this->count() != 0) { 721 | foreach ($this->getItems() as $item) { 722 | $total += $item->subTotal(false); 723 | } 724 | } 725 | 726 | if ($total < 0) { 727 | $total = 0; 728 | } 729 | 730 | return $this->formatMoney($total, null, null, $format); 731 | } 732 | 733 | /** 734 | * Get the count based on qty, or number of unique items. 735 | * 736 | * @param bool $withItemQty 737 | * 738 | * @return int 739 | */ 740 | public function count($withItemQty = true) 741 | { 742 | $count = 0; 743 | 744 | foreach ($this->getItems() as $item) { 745 | if ($withItemQty) { 746 | $count += $item->qty; 747 | } else { 748 | $count++; 749 | } 750 | } 751 | 752 | return $count; 753 | } 754 | 755 | /** 756 | * Formats the number into a money format based on the locale and currency formats. 757 | * 758 | * @param $number 759 | * @param $locale 760 | * @param $currencyCode 761 | * @param $format 762 | * 763 | * @return string 764 | */ 765 | public static function formatMoney($number, $locale = null, $currencyCode = null, $format = true) 766 | { 767 | // When prices in cents needs to be formatted, divide by 100 to allow formatting in whole units 768 | if (config('laracart.prices_in_cents', false) === true && $format) { 769 | $number = $number / 100; 770 | // When prices in cents do not need to be formatted then cast to integer and round the price 771 | } elseif (config('laracart.prices_in_cents', false) === true && !$format) { 772 | $number = (int) round($number); 773 | } else { 774 | $number = round($number, 2); 775 | } 776 | 777 | if ($format) { 778 | $moneyFormatter = new NumberFormatter(empty($locale) ? config('laracart.locale', 'en_US.UTF-8') : $locale, NumberFormatter::CURRENCY); 779 | 780 | $number = $moneyFormatter->formatCurrency($number, empty($currencyCode) ? config('laracart.currency_code', 'USD') : $currencyCode); 781 | } 782 | 783 | return $number; 784 | } 785 | 786 | public function feeSubTotal($format = true) 787 | { 788 | $feeTotal = 0; 789 | 790 | foreach ($this->getFees() as $fee) { 791 | $feeTotal += $fee->amount; 792 | } 793 | 794 | return $this->formatMoney($feeTotal, null, null, $format); 795 | } 796 | 797 | /** 798 | * Gets all the fees on the cart object. 799 | * 800 | * @return mixed 801 | */ 802 | public function getFees() 803 | { 804 | return $this->cart->fees; 805 | } 806 | 807 | /** 808 | * Gets the total amount discounted. 809 | * 810 | * @param bool $format 811 | * 812 | * @return string 813 | */ 814 | public function discountTotal($format = true) 815 | { 816 | $total = 0; 817 | 818 | foreach ($this->getItems() as $item) { 819 | $total += $item->getDiscount(false); 820 | } 821 | 822 | foreach ($this->getFees() as $fee) { 823 | $total += $fee->getDiscount(false); 824 | } 825 | 826 | return $this->formatMoney($total, null, null, $format); 827 | } 828 | 829 | /** 830 | * Checks to see if its an item model. 831 | * 832 | * @param $itemModel 833 | * 834 | * @return bool 835 | */ 836 | private function isItemModel($itemModel) 837 | { 838 | if (is_object($itemModel) && get_class($itemModel) == config('laracart.item_model')) { 839 | return true; 840 | } 841 | 842 | return false; 843 | } 844 | 845 | /** 846 | * Gets the item models options based the config. 847 | * 848 | * @param Model $itemModel 849 | * @param array $options 850 | * 851 | * @return array 852 | */ 853 | private function getItemModelOptions(Model $itemModel, $options = []) 854 | { 855 | $itemOptions = []; 856 | foreach ($options as $option) { 857 | $itemOptions[$option] = $this->getFromModel($itemModel, $option); 858 | } 859 | 860 | return array_filter($itemOptions, function ($value) { 861 | if ($value !== false && empty($value)) { 862 | return false; 863 | } 864 | 865 | return true; 866 | }); 867 | } 868 | 869 | /** 870 | * Gets a option from the model. 871 | * 872 | * @param Model $itemModel 873 | * @param $attr 874 | * @param null $defaultValue 875 | * 876 | * @return Model|null 877 | */ 878 | private function getFromModel(Model $itemModel, $attr, $defaultValue = null) 879 | { 880 | $variable = $itemModel; 881 | 882 | if (!empty($attr)) { 883 | foreach (explode('.', $attr) as $attr) { 884 | $variable = Arr::get($variable, $attr, $defaultValue); 885 | } 886 | } 887 | 888 | return $variable; 889 | } 890 | 891 | /** 892 | * Removes a coupon from the item. 893 | * 894 | * @param null $code 895 | */ 896 | private function removeCouponFromItems($code = null) 897 | { 898 | foreach ($this->getItems() as $item) { 899 | if (isset($item->coupon) && (empty($code) || $item->coupon->code == $code)) { 900 | $item->coupon = null; 901 | } 902 | } 903 | } 904 | } 905 | -------------------------------------------------------------------------------- /src/LaraCartHasher.php: -------------------------------------------------------------------------------- 1 | publishes([ 21 | __DIR__.'/config/laracart.php' => config_path('laracart.php'), 22 | ]); 23 | 24 | $this->mergeConfigFrom( 25 | __DIR__.'/config/laracart.php', 26 | 'laracart' 27 | ); 28 | 29 | if (!$this->migrationHasAlreadyBeenPublished()) { 30 | $this->publishes([ 31 | __DIR__.'/database/migrations/add_cart_session_id_to_users_table.php.stub' => database_path('migrations/'.date('Y_m_d_His').'_add_cart_session_id_to_users_table.php'), 32 | ], 'migrations'); 33 | } 34 | } 35 | 36 | /** 37 | * Register the service provider. 38 | * 39 | * @return void 40 | */ 41 | public function register() 42 | { 43 | $this->app->singleton(LaraCart::SERVICE, function ($app) { 44 | return new LaraCart($app['session'], $app['events'], $app['auth']); 45 | }); 46 | 47 | $this->app->singleton(LaraCart::HASH, function () { 48 | return new LaraCartHasher(); 49 | }); 50 | 51 | $this->app->bind( 52 | LaraCart::RANHASH, 53 | function () { 54 | return Str::random(40); 55 | } 56 | ); 57 | } 58 | 59 | /** 60 | * Checks to see if the migration has already been published. 61 | * 62 | * @return bool 63 | */ 64 | protected function migrationHasAlreadyBeenPublished() 65 | { 66 | $files = glob(database_path('migrations/*_add_cart_session_id_to_users_table.php')); 67 | 68 | return count($files) > 0; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Traits/CartOptionsMagicMethodsTrait.php: -------------------------------------------------------------------------------- 1 | options, $option); 28 | } 29 | 30 | /** 31 | * Magic Method allows for user input to set a value inside the options array. 32 | * 33 | * @param $option 34 | * @param $value 35 | * 36 | * @throws InvalidPrice 37 | * @throws InvalidQuantity 38 | * @throws InvalidTaxableValue 39 | */ 40 | public function __set($option, $value) 41 | { 42 | switch ($option) { 43 | case CartItem::ITEM_QTY: 44 | if (!is_numeric($value) || $value <= 0) { 45 | throw new InvalidQuantity('The quantity must be a valid number'); 46 | } 47 | break; 48 | case CartItem::ITEM_PRICE: 49 | if (!is_numeric($value)) { 50 | throw new InvalidPrice('The price must be a valid number'); 51 | } 52 | break; 53 | case CartItem::ITEM_TAX: 54 | if (!empty($value) && (!is_numeric($value))) { 55 | throw new InvalidTaxableValue('The tax must be a number'); 56 | } 57 | break; 58 | case CartItem::ITEM_TAXABLE: 59 | if (!is_bool($value) && $value != 0 && $value != 1) { 60 | throw new InvalidTaxableValue('The taxable option must be a boolean'); 61 | } 62 | break; 63 | } 64 | 65 | $changed = (!empty(Arr::get($this->options, $option)) && Arr::get($this->options, $option) != $value); 66 | Arr::set($this->options, $option, $value); 67 | 68 | if ($changed) { 69 | if (is_callable([$this, 'generateHash'])) { 70 | $this->generateHash(); 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * Magic Method allows for user to check if an option isset. 77 | * 78 | * @param $option 79 | * 80 | * @return bool 81 | */ 82 | public function __isset($option) 83 | { 84 | if (isset($this->options[$option])) { 85 | return true; 86 | } else { 87 | return false; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Traits/CouponTrait.php: -------------------------------------------------------------------------------- 1 | $value) { 31 | $this->$key = $value; 32 | } 33 | } 34 | 35 | /** 36 | * Checks to see if we can apply the coupon. 37 | * 38 | * @return bool 39 | */ 40 | public function canApply() 41 | { 42 | $this->message = 'Coupon Applied'; 43 | 44 | return true; 45 | } 46 | 47 | /** 48 | * Checks the minimum subtotal needed to apply the coupon. 49 | * 50 | * @param $minAmount 51 | * @param $throwErrors 52 | * 53 | * @throws CouponException 54 | * 55 | * @return bool 56 | */ 57 | public function checkMinAmount($minAmount, $throwErrors = true) 58 | { 59 | $laraCart = \App::make(LaraCart::SERVICE); 60 | 61 | if ($laraCart->subTotal(false) >= $minAmount) { 62 | return true; 63 | } else { 64 | if ($throwErrors) { 65 | throw new CouponException('You must have at least a total of '.$laraCart->formatMoney($minAmount)); 66 | } else { 67 | return false; 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * Returns either the max discount or the discount applied based on what is passed through. 74 | * 75 | * @param $maxDiscount 76 | * @param $discount 77 | * @param $throwErrors 78 | * 79 | * @throws CouponException 80 | * 81 | * @return mixed 82 | */ 83 | public function maxDiscount($maxDiscount, $discount, $throwErrors = true) 84 | { 85 | if ($maxDiscount == 0 || $maxDiscount > $discount) { 86 | return $discount; 87 | } else { 88 | if ($throwErrors) { 89 | throw new CouponException('This has a max discount of '.\App::make(\LukePOLO\Laracart\LaraCart::SERVICE)->formatMoney($maxDiscount)); 90 | } else { 91 | return $maxDiscount; 92 | } 93 | } 94 | } 95 | 96 | /** 97 | * Checks to see if the times are valid for the coupon. 98 | * 99 | * @param Carbon $startDate 100 | * @param Carbon $endDate 101 | * @param $throwErrors 102 | * 103 | * @throws CouponException 104 | * 105 | * @return bool 106 | */ 107 | public function checkValidTimes(Carbon $startDate, Carbon $endDate, $throwErrors = true) 108 | { 109 | if (Carbon::now()->between($startDate, $endDate)) { 110 | return true; 111 | } else { 112 | if ($throwErrors) { 113 | throw new CouponException('This coupon has expired'); 114 | } else { 115 | return false; 116 | } 117 | } 118 | } 119 | 120 | /** 121 | * Sets a discount to an item with what code was used and the discount amount. 122 | * 123 | * @param CartItem $item 124 | */ 125 | public function setDiscountOnItem(CartItem $item) 126 | { 127 | $this->appliedToCart = false; 128 | $item->coupon = $this; 129 | $item->update(); 130 | } 131 | 132 | public function discounted() 133 | { 134 | return $this->discounted; 135 | } 136 | 137 | public function getMessage() 138 | { 139 | return $this->message; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/config/laracart.php: -------------------------------------------------------------------------------- 1 | 'laracart', 12 | 13 | /* 14 | |-------------------------------------------------------------------------- 15 | | database settings 16 | |-------------------------------------------------------------------------- 17 | | 18 | | Here you can set the name of the table you want to use for 19 | | storing and restoring the cart session id. 20 | | 21 | */ 22 | 'database' => [ 23 | 'table' => 'users', 24 | ], 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Locale is used to convert money into a readable format for the user, 29 | | please note the UTF-8, helps to make sure its encoded correctly 30 | | 31 | | Common Locales 32 | | 33 | | English - United States (en_US): 123,456.00 34 | | English - United Kingdom (en_GB) 123,456.00 35 | | Spanish - Spain (es_ES): 123.456,000 36 | | Dutch - Netherlands (nl_NL): 123 456,00 37 | | German - Germany (de_DE): 123.456,00 38 | | French - France (fr_FR): 123 456,00 39 | | Italian - Italy (it_IT): 123.456,00 40 | | 41 | | This site is pretty useful : http://lh.2xlibre.net/locales/ 42 | | 43 | |-------------------------------------------------------------------------- 44 | | 45 | */ 46 | 'locale' => 'en_US.UTF-8', 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | The currency code changes how you see the actual amounts. 51 | |-------------------------------------------------------------------------- 52 | | This is the list of all valid currency codes 53 | | https://www2.1010data.com/documentationcenter/prod/1010dataReferenceManual/DataTypesAndFormats/currencyUnitCodes.html 54 | | 55 | */ 56 | 'currency_code' => 'USD', 57 | 58 | /* 59 | |-------------------------------------------------------------------------- 60 | | If true, lets you supply and retrieve all prices in cents. 61 | | To retrieve the prices as integer in cents, set the $format parameter 62 | | to false for the various price functions. Otherwise you will retrieve 63 | | the formatted price instead. 64 | | Make sure when adding products to the cart, adding coupons, etc, to 65 | | supply the price in cents too. 66 | |-------------------------------------------------------------------------- 67 | | 68 | */ 69 | 'prices_in_cents' => false, 70 | 71 | /* 72 | |-------------------------------------------------------------------------- 73 | | Sets the tax for the cart and items, you can change per item 74 | | via the object later if needed 75 | |-------------------------------------------------------------------------- 76 | | 77 | */ 78 | 'tax' => null, 79 | 80 | /* 81 | |-------------------------------------------------------------------------- 82 | | Allows you to choose if the discounts applied to fees 83 | |-------------------------------------------------------------------------- 84 | | 85 | */ 86 | 'fees_taxable' => false, 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Allows you to choose if the discounts applied to fees 91 | |-------------------------------------------------------------------------- 92 | | 93 | */ 94 | 'discount_fees' => false, 95 | 96 | /* 97 | |-------------------------------------------------------------------------- 98 | | Allows you to configure if a user can apply multiple coupons 99 | |-------------------------------------------------------------------------- 100 | | 101 | */ 102 | 'multiple_coupons' => false, 103 | 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | The default item model for your relations 107 | |-------------------------------------------------------------------------- 108 | | 109 | */ 110 | 'item_model' => null, 111 | 112 | /* 113 | |-------------------------------------------------------------------------- 114 | | Binds your data into the correct spots for LaraCart 115 | |-------------------------------------------------------------------------- 116 | | 117 | */ 118 | 'item_model_bindings' => [ 119 | \LukePOLO\LaraCart\CartItem::ITEM_ID => 'id', 120 | \LukePOLO\LaraCart\CartItem::ITEM_NAME => 'name', 121 | \LukePOLO\LaraCart\CartItem::ITEM_PRICE => 'price', 122 | \LukePOLO\LaraCart\CartItem::ITEM_TAXABLE => 'taxable', 123 | \LukePOLO\LaraCart\CartItem::ITEM_OPTIONS => [ 124 | // put columns here for additional options, 125 | // these will be merged with options that are passed in 126 | // e.x 127 | // tax => .07 128 | ], 129 | ], 130 | 131 | /* 132 | |-------------------------------------------------------------------------- 133 | | The default item relations to the item_model 134 | |-------------------------------------------------------------------------- 135 | | 136 | */ 137 | 'item_model_relations' => [], 138 | 139 | /* 140 | |-------------------------------------------------------------------------- 141 | | This allows you to use multiple devices based on your logged in user 142 | |-------------------------------------------------------------------------- 143 | | 144 | */ 145 | 'cross_devices' => false, 146 | 147 | /* 148 | |-------------------------------------------------------------------------- 149 | | This allows you to use custom guard to get logged in user 150 | |-------------------------------------------------------------------------- 151 | | 152 | */ 153 | 'guard' => null, 154 | 155 | /* 156 | |-------------------------------------------------------------------------- 157 | | This allows you to exclude any option from generating CartItem hash 158 | |-------------------------------------------------------------------------- 159 | | 160 | */ 161 | 'exclude_from_hash' => [], 162 | ]; 163 | -------------------------------------------------------------------------------- /src/database/migrations/add_cart_session_id_to_users_table.php.stub: -------------------------------------------------------------------------------- 1 | string('cart_session_id')->nullable()->default(null); 18 | }); 19 | } 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | if ((Schema::hasColumn(config('laracart.database.table'), 'cart_session_id'))) { 30 | Schema::table(config('laracart.database.table'), function (Blueprint $table) { 31 | $table->dropColumn('cart_session_id'); 32 | }); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Coupons/Fixed.php: -------------------------------------------------------------------------------- 1 | code = $code; 30 | $this->value = $value; 31 | 32 | $this->setOptions($options); 33 | } 34 | 35 | /** 36 | * Gets the discount amount. 37 | * 38 | * that way we can spit out why the coupon has failed 39 | * 40 | * @return string 41 | */ 42 | public function discount($price) 43 | { 44 | if ($this->canApply()) { 45 | return 100; 46 | } 47 | 48 | return 0; 49 | } 50 | 51 | /** 52 | * Checks if you can apply the coupon. 53 | * 54 | * @throws CouponException 55 | * 56 | * @return bool 57 | */ 58 | public function canApply($throw = false) 59 | { 60 | if ($this->discounted === 0) { 61 | throw new CouponException('Sorry, you must have at least 100 dollars!'); 62 | } 63 | 64 | return true; 65 | } 66 | 67 | /** 68 | * Displays the value in a money format. 69 | * 70 | * @param null $locale 71 | * @param null $currencyCode 72 | * 73 | * @return string 74 | */ 75 | public function displayValue($locale = null, $currencyCode = null) 76 | { 77 | return LaraCart::formatMoney( 78 | $this->discount(), 79 | $locale, 80 | $currencyCode 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/CouponsTest.php: -------------------------------------------------------------------------------- 1 | addItem(3, 1); 15 | 16 | try { 17 | $percentCoupon = new LukePOLO\LaraCart\Coupons\Percentage('10%OFF', '23'); 18 | $this->expectException(\LukePOLO\LaraCart\Exceptions\CouponException::class); 19 | } catch (\LukePOLO\LaraCart\Exceptions\CouponException $e) { 20 | $this->assertEquals('Invalid value for a percentage coupon. The value must be between 0 and 1.', $e->getMessage()); 21 | } 22 | } 23 | 24 | /** 25 | * Test the percentage coupons. 26 | */ 27 | public function testAddPercentageCoupon() 28 | { 29 | $this->addItem(3, 1); 30 | 31 | $percentCoupon = new LukePOLO\LaraCart\Coupons\Percentage('10%OFF', '.1'); 32 | 33 | $this->laracart->addCoupon($percentCoupon); 34 | 35 | $this->assertEquals($percentCoupon, $this->laracart->findCoupon('10%OFF')); 36 | 37 | $this->assertEquals('10%', $percentCoupon->displayValue()); 38 | $this->assertEquals('0.30', $this->laracart->discountTotal(false)); 39 | 40 | $this->assertCount(1, $this->laracart->getCoupons()); 41 | 42 | $this->assertEquals(3, $this->laracart->subTotal(false)); 43 | $this->assertEquals(.19, $this->laracart->taxTotal(false)); 44 | } 45 | 46 | /** 47 | * Test the percentage coupons on item with tax. 48 | */ 49 | public function testAddPercentageCouponOnTaxItem() 50 | { 51 | $item = $this->addItem(1, 10); 52 | 53 | $percentCoupon = new LukePOLO\LaraCart\Coupons\Percentage('10%OFF', '.1'); 54 | 55 | $this->laracart->addCoupon($percentCoupon); 56 | $percentCoupon->setDiscountOnItem($item); 57 | 58 | $this->assertEquals($percentCoupon, $this->laracart->findCoupon('10%OFF')); 59 | 60 | $this->assertEquals('10%', $percentCoupon->displayValue()); 61 | $this->assertEquals(1, $percentCoupon->discount($item->price)); 62 | $this->assertEquals(.63, $this->laracart->taxTotal(false)); 63 | $this->assertEquals(9.63, $this->laracart->total(false)); 64 | 65 | $this->assertCount(1, $this->laracart->getCoupons()); 66 | } 67 | 68 | /** 69 | * Test the fixed coupons. 70 | */ 71 | public function testAddFixedCoupon() 72 | { 73 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 74 | 75 | $this->laracart->addCoupon($fixedCoupon); 76 | $this->addItem(1, 20); 77 | $this->assertEquals('10.00', $this->laracart->discountTotal(false)); 78 | $this->assertEquals('0.70', $this->laracart->taxTotal(false)); 79 | } 80 | 81 | /** 82 | * Test the fixed coupons. 83 | */ 84 | public function testFixedCoupon() 85 | { 86 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 87 | 88 | $this->laracart->addCoupon($fixedCoupon); 89 | $this->assertEquals($fixedCoupon, $this->laracart->findCoupon('10OFF')); 90 | } 91 | 92 | /** 93 | * Test if single coupons works, we souldn't be able to add two. 94 | */ 95 | public function testSingleCoupons() 96 | { 97 | $fixedCouponOne = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 98 | $fixedCouponTwo = new LukePOLO\LaraCart\Coupons\Fixed('5OFF', 5); 99 | 100 | $this->laracart->addCoupon($fixedCouponOne); 101 | $this->laracart->addCoupon($fixedCouponTwo); 102 | 103 | $this->assertCount(1, $this->laracart->getCoupons()); 104 | 105 | $this->assertEquals($fixedCouponTwo, $this->laracart->findCoupon('5OFF')); 106 | } 107 | 108 | /** 109 | * Test if we can add multiple if the config is set properly. 110 | */ 111 | public function testMultipleCoupons() 112 | { 113 | $cart = $this->laracart->get()->cart; 114 | $cart->multipleCoupons = true; 115 | 116 | $fixedCouponOne = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 117 | $fixedCouponTwo = new LukePOLO\LaraCart\Coupons\Fixed('5OFF', 5); 118 | 119 | $this->laracart->addCoupon($fixedCouponOne); 120 | $this->laracart->addCoupon($fixedCouponTwo); 121 | 122 | $this->assertCount(2, $this->laracart->getCoupons()); 123 | } 124 | 125 | /** 126 | * Test removing coupons. 127 | */ 128 | public function testRemoveCoupon() 129 | { 130 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 131 | $this->laracart->addCoupon($fixedCoupon); 132 | 133 | $this->assertEquals($fixedCoupon, $this->laracart->findCoupon('10OFF')); 134 | 135 | $this->laracart->removeCoupon('10OFF'); 136 | 137 | $this->assertEmpty($this->laracart->findCoupon('10OFF')); 138 | } 139 | 140 | /** 141 | * Test getting the message from the coupon to see if its valid or has an error. 142 | */ 143 | public function testGetMessage() 144 | { 145 | $this->addItem(); 146 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 147 | $this->laracart->addCoupon($fixedCoupon); 148 | 149 | $foundCoupon = $this->laracart->findCoupon('10OFF'); 150 | $this->assertEquals('Coupon Applied', $foundCoupon->getMessage()); 151 | $this->assertEquals(true, $foundCoupon->canApply()); 152 | } 153 | 154 | /** 155 | * Test the min amount for a coupon. 156 | */ 157 | public function testCheckMinAmount() 158 | { 159 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10, [ 160 | 'addOptions' => 1, 161 | ]); 162 | 163 | $this->laracart->addCoupon($fixedCoupon); 164 | 165 | $coupon = $this->laracart->findCoupon('10OFF'); 166 | 167 | $this->assertEquals(true, $coupon->checkMinAmount(0)); 168 | $this->assertEquals(false, $coupon->checkMinAmount(100, false)); 169 | $this->assertEquals(1, $coupon->addOptions); 170 | 171 | try { 172 | $coupon->checkMinAmount(100); 173 | $this->expectException(\LukePOLO\LaraCart\Exceptions\CouponException::class); 174 | } catch (\LukePOLO\LaraCart\Exceptions\CouponException $e) { 175 | $this->assertEquals('You must have at least a total of $100.00', $e->getMessage()); 176 | } 177 | } 178 | 179 | /** 180 | * Test the max discount for a coupon. 181 | */ 182 | public function testMaxDiscount() 183 | { 184 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 185 | $this->laracart->addCoupon($fixedCoupon); 186 | 187 | $coupon = $this->laracart->findCoupon('10OFF'); 188 | 189 | $this->assertEquals(100, $coupon->maxDiscount(0, 100)); 190 | $this->assertEquals(100, $coupon->maxDiscount(5000, 100)); 191 | $this->assertEquals(1, $coupon->maxDiscount(1, 100, false)); 192 | 193 | try { 194 | $coupon->maxDiscount(10, 100); 195 | $this->expectException(\LukePOLO\LaraCart\Exceptions\CouponException::class); 196 | } catch (\LukePOLO\LaraCart\Exceptions\CouponException $e) { 197 | $this->assertEquals('This has a max discount of $10.00', $e->getMessage()); 198 | } 199 | } 200 | 201 | /** 202 | * Test the valid times for a coupon. 203 | */ 204 | public function testCheckValidTimes() 205 | { 206 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 207 | $this->laracart->addCoupon($fixedCoupon); 208 | 209 | $coupon = $this->laracart->findCoupon('10OFF'); 210 | 211 | $this->assertEquals(true, $coupon->checkValidTimes(Carbon::yesterday(), Carbon::tomorrow())); 212 | $this->assertEquals(false, $coupon->checkValidTimes(Carbon::tomorrow(), Carbon::tomorrow(), false)); 213 | 214 | try { 215 | $this->assertEquals(false, $coupon->checkValidTimes(Carbon::tomorrow(), Carbon::tomorrow())); 216 | $this->expectException(\LukePOLO\LaraCart\Exceptions\CouponException::class); 217 | } catch (\LukePOLO\LaraCart\Exceptions\CouponException $e) { 218 | $this->assertEquals('This coupon has expired', $e->getMessage()); 219 | } 220 | } 221 | 222 | /** 223 | * Test if we can set a coupon on an item. 224 | */ 225 | public function testSetDiscountOnItem() 226 | { 227 | $item = $this->addItem(1, 100); 228 | 229 | $this->assertEquals(107, $this->laracart->total(false)); 230 | 231 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 232 | 233 | $fixedCoupon->setDiscountOnItem($item); 234 | 235 | $this->assertNotNull($item->coupon); 236 | 237 | $this->assertEquals('10OFF', $item->coupon->code); 238 | $this->assertEquals(90 * 1.07, $this->laracart->total(false)); 239 | 240 | $this->laracart->removeCoupon('10OFF'); 241 | 242 | $this->assertNull($item->coupon); 243 | $this->assertEquals(0, $item->discount); 244 | 245 | $this->assertEquals(0, $this->laracart->discountTotal(false)); 246 | $this->assertEquals(107, $this->laracart->total(false)); 247 | } 248 | 249 | public function testDiscountsTaxable() 250 | { 251 | $this->addItem(1, 20); 252 | 253 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 254 | 255 | $this->laracart->addCoupon($fixedCoupon); 256 | 257 | $this->laracart->findCoupon('10OFF'); 258 | 259 | $this->assertEquals(20, $this->laracart->subTotal(false)); 260 | 261 | $this->assertEquals(10 + (10 * .07), $this->laracart->total(false)); 262 | } 263 | 264 | /** 265 | * Test if we can set a coupon on an item. 266 | */ 267 | public function testDiscountTotals() 268 | { 269 | $item = $this->addItem(1, 10); 270 | 271 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 272 | 273 | $this->laracart->addCoupon($fixedCoupon); 274 | 275 | $coupon = $this->laracart->findCoupon('10OFF'); 276 | 277 | $coupon->setDiscountOnItem($item); 278 | 279 | $this->assertEquals('10OFF', $item->coupon->code); 280 | 281 | $this->assertEquals(0, $this->laracart->total(false)); 282 | $this->assertEquals(10, $this->laracart->discountTotal(false)); 283 | } 284 | 285 | /** 286 | * Test cart percentage coupon when items are not taxable. 287 | */ 288 | public function testCouponsNotTaxableItem() 289 | { 290 | $this->addItem(1, 1, false); 291 | 292 | $percentCoupon = new LukePOLO\LaraCart\Coupons\Percentage('20%OFF', '.2'); 293 | 294 | $this->laracart->addCoupon($percentCoupon); 295 | 296 | $this->assertEquals($percentCoupon, $this->laracart->findCoupon('20%OFF')); 297 | 298 | $this->assertEquals('20%', $percentCoupon->displayValue()); 299 | $this->assertEquals('0.20', $this->laracart->discountTotal(false)); 300 | 301 | $this->assertEquals(0, $this->laracart->taxTotal(false)); 302 | 303 | $this->assertEquals('0.80', $this->laracart->total(false)); 304 | } 305 | 306 | /** 307 | * Test cart percentage coupon when items are taxable. 308 | */ 309 | public function testCouponsTaxableItem() 310 | { 311 | $this->addItem(); 312 | 313 | $percentCoupon = new LukePOLO\LaraCart\Coupons\Percentage('20%OFF', '.2'); 314 | 315 | $this->laracart->addCoupon($percentCoupon); 316 | 317 | $this->assertEquals('20%', $percentCoupon->displayValue()); 318 | $this->assertEquals('0.20', $this->laracart->discountTotal(false)); 319 | 320 | $this->assertEquals('0.06', $this->laracart->taxTotal(false)); 321 | $this->assertEquals('0.86', $this->laracart->total(false)); 322 | } 323 | 324 | /** 325 | * Test if we can remove all coupons from the cart. 326 | */ 327 | public function testRemoveCoupons() 328 | { 329 | $item = $this->addItem(2, 30); 330 | 331 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 332 | 333 | $this->laracart->addCoupon($fixedCoupon); 334 | 335 | $coupon = $this->laracart->findCoupon('10OFF'); 336 | 337 | $coupon->setDiscountOnItem($item); 338 | 339 | $this->assertEquals('10OFF', $item->coupon->code); 340 | 341 | $this->laracart->removeCoupons(); 342 | 343 | $this->assertEmpty($this->laracart->getCoupons()); 344 | 345 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 346 | $this->laracart->addCoupon($fixedCoupon); 347 | 348 | $this->assertEquals($fixedCoupon, $this->laracart->findCoupon('10OFF')); 349 | 350 | $this->laracart->removeCoupons(); 351 | 352 | $this->assertEmpty($this->laracart->findCoupon('10OFF')); 353 | 354 | $cart = $this->laracart->get()->cart; 355 | $cart->multipleCoupons = true; 356 | 357 | $fixedCouponOne = new LukePOLO\LaraCart\Coupons\Fixed('10OFF', 10); 358 | $fixedCouponTwo = new LukePOLO\LaraCart\Coupons\Fixed('5OFF', 5); 359 | 360 | $this->laracart->addCoupon($fixedCouponOne); 361 | $this->laracart->addCoupon($fixedCouponTwo); 362 | 363 | $this->assertCount(2, $this->laracart->getCoupons()); 364 | 365 | $this->laracart->removeCoupons(); 366 | 367 | $this->assertEmpty($this->laracart->getCoupons()); 368 | } 369 | 370 | /** 371 | * Testing getting the message on a coupon. 372 | */ 373 | public function testCouponMessage() 374 | { 375 | $item = $this->addItem(2, 30); 376 | $fixedCoupon = new \LukePOLO\LaraCart\Tests\Coupons\Fixed('10OFF', 10); 377 | 378 | try { 379 | $this->assertNotEquals(true, $fixedCoupon->discount($item->price)); 380 | } catch (\LukePOLO\LaraCart\Exceptions\CouponException $e) { 381 | $this->assertEquals('Sorry, you must have at least 100 dollars!', $e->getMessage()); 382 | } 383 | } 384 | 385 | /** 386 | * Testing discount when total is greater than applied coupon value. 387 | */ 388 | public function testFixedCouponWithTotalLessThanCoupon() 389 | { 390 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('500 OFF', 500); 391 | 392 | $this->laracart->addCoupon($fixedCoupon); 393 | 394 | $this->assertEquals('0.00', $this->laracart->discountTotal(false)); 395 | 396 | $this->addItem(1, 400); 397 | 398 | $this->assertEquals('400.00', $this->laracart->discountTotal(false)); 399 | } 400 | 401 | /** 402 | * Testing discount when total with fees is greater than applied coupon value. 403 | */ 404 | public function testFixedCouponWithFeeWithTotalLessThanCoupon() 405 | { 406 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('500 OFF', 500); 407 | 408 | $this->laracart->addCoupon($fixedCoupon); 409 | 410 | $this->addItem(1, 400); 411 | 412 | $this->laracart->addFee('testFee', 150); 413 | 414 | $this->assertEquals('400.00', $this->laracart->discountTotal(false)); 415 | 416 | $this->assertEquals(150, $this->laracart->total(false)); 417 | 418 | $percentCoupon = new LukePOLO\LaraCart\Coupons\Percentage('100% Off', 1); 419 | $this->laracart->addCoupon($percentCoupon); 420 | 421 | $this->assertEquals(150, $this->laracart->total(false)); 422 | } 423 | 424 | public function testFeeDiscount() 425 | { 426 | $this->app['config']->set('laracart.discount_fees', true); 427 | 428 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('10 OFF', 10); 429 | 430 | $this->laracart->addCoupon($fixedCoupon); 431 | 432 | $this->addItem(1, 5); 433 | 434 | $this->laracart->addFee('testFee', 15); 435 | 436 | $this->assertEquals(10, $this->laracart->total(false)); 437 | } 438 | 439 | /** 440 | * Testing percentage coupon on multiple item qty. 441 | */ 442 | public function testPercentageCouponOnMultipleQtyItems() 443 | { 444 | $percentCoupon = new LukePOLO\LaraCart\Coupons\Percentage('10% Off', .1); 445 | 446 | $item = $this->addItem(2, 10); 447 | 448 | $percentCoupon->setDiscountOnItem($item); 449 | 450 | $this->assertEquals(18, $this->laracart->total(false) - $this->laracart->taxTotal(false)); 451 | } 452 | 453 | /** 454 | * Testing Discount Pre Taxed. 455 | */ 456 | public function testPreTaxDiscountFixed() 457 | { 458 | $this->app['config']->set('laracart.tax', .19); 459 | $this->app['config']->set('laracart.fees_taxable', false); 460 | 461 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('$1 Off', 1); 462 | 463 | $this->addItem(1, .84); 464 | 465 | $this->laracart->addCoupon($fixedCoupon); 466 | 467 | $this->assertEquals(0, $this->laracart->total(false)); 468 | } 469 | 470 | /** 471 | * Testing Discount Pre Taxed. 472 | */ 473 | public function testPreTaxDiscountPercentage() 474 | { 475 | $this->app['config']->set('laracart.tax', .19); 476 | $this->app['config']->set('laracart.fees_taxable', false); 477 | 478 | $percentageCoupon = new LukePOLO\LaraCart\Coupons\Percentage('100%', 1); 479 | 480 | $this->addItem(1, .84); 481 | 482 | $this->laracart->addCoupon($percentageCoupon); 483 | 484 | $this->assertEquals(0, $this->laracart->total(false)); 485 | } 486 | 487 | public function testCartVsItemCoupon() 488 | { 489 | $item = $this->addItem(); 490 | $couponPercentage = new \LukePOLO\LaraCart\Coupons\Percentage('50%', 0.5); 491 | $this->laracart->addCoupon($couponPercentage); 492 | 493 | $cartTotal = $this->laracart->total(false); 494 | $this->laracart->removeCoupon($couponPercentage->code); 495 | 496 | $this->assertEquals(1.07, $this->laracart->total(false)); 497 | 498 | $item->addCoupon($couponPercentage); 499 | 500 | $itemTotal = $this->laracart->total(false); 501 | 502 | $this->assertEquals(.54, $cartTotal); 503 | $this->assertEquals($itemTotal, $cartTotal); 504 | } 505 | 506 | public function testCouponOnSubItems() 507 | { 508 | $item = $this->addItem(1, 0); 509 | 510 | $item->addSubItem([ 511 | 'size' => 'XXL', 512 | 'price' => 5, 513 | ]); 514 | 515 | $this->assertEquals(5, $this->laracart->subTotal(false)); 516 | 517 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed('5 OFF', 5); 518 | 519 | $this->laracart->addCoupon($fixedCoupon); 520 | 521 | $this->assertEquals(0, $this->laracart->total(false)); 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /tests/CrossDeviceTest.php: -------------------------------------------------------------------------------- 1 | assertNotEmpty($this->artisan('migrate')); 17 | 18 | $this->beforeApplicationDestroyed(function () { 19 | $this->artisan('migrate:rollback'); 20 | }); 21 | } 22 | 23 | /** 24 | * Test getting the old session. 25 | */ 26 | public function testGetOldSession() 27 | { 28 | $newCart = new \LukePOLO\LaraCart\LaraCart($this->session, $this->events, $this->authManager); 29 | 30 | $this->addItem(); 31 | $this->addItem(); 32 | 33 | $this->app['config']->set('laracart.cross_devices', true); 34 | 35 | $user = new \LukePOLO\LaraCart\Tests\Models\User(); 36 | 37 | $this->assertEquals(0, $newCart->count(false)); 38 | $this->assertEquals(1, $this->count(false)); 39 | 40 | $user->cart_session_id = $this->session->getId(); 41 | $this->authManager->login($user); 42 | 43 | $newCart->get(); 44 | 45 | $this->assertEquals($newCart->count(false), $this->count(false)); 46 | } 47 | 48 | /** 49 | * Testing to make sure the session gets saved to the model. 50 | */ 51 | public function testSaveCartSessionID() 52 | { 53 | $this->app['config']->set('laracart.cross_devices', true); 54 | $user = new \LukePOLO\LaraCart\Tests\Models\User(); 55 | $this->authManager->login($user); 56 | 57 | $this->addItem(); 58 | 59 | $this->assertEquals($this->session->getId(), $this->authManager->user()->cart_session_id); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/FeesTest.php: -------------------------------------------------------------------------------- 1 | laracart->addFee( 19 | $name, 20 | $fee 21 | ); 22 | } 23 | 24 | /** 25 | * Add a fee with tax. 26 | * 27 | * @param $name 28 | * @param int $fee 29 | */ 30 | private function addFeeTax($name, $fee = 100, $tax = 0.21) 31 | { 32 | $this->laracart->addFee( 33 | $name, 34 | $fee, 35 | true, 36 | [ 37 | 'tax' => $tax, 38 | ] 39 | ); 40 | } 41 | 42 | /** 43 | * Testing add a fee to the cart. 44 | */ 45 | public function testAddFee() 46 | { 47 | $this->addFee('testFeeOne'); 48 | 49 | $fee = $this->laracart->getFee('testFeeOne'); 50 | 51 | $this->assertEquals('$10.00', $fee->getAmount()); 52 | $this->assertEquals(10, $fee->getAmount(false)); 53 | } 54 | 55 | /** 56 | * Testing add a fee to the cart with tax. 57 | */ 58 | public function testAddFeeTax() 59 | { 60 | $this->addFeeTax('testFeeOne'); 61 | 62 | $fee = $this->laracart->getFee('testFeeOne'); 63 | 64 | $this->assertEquals('$100.00', $fee->getAmount(true, false)); 65 | $this->assertEquals('$121.00', $fee->getAmount(true, true)); 66 | 67 | $this->assertEquals(100, $fee->getAmount(false, false)); 68 | $this->assertEquals(121, $fee->getAmount(false, true)); 69 | } 70 | 71 | /** 72 | * Test if we can add multiple fees to the cart. 73 | */ 74 | public function testMultipleFees() 75 | { 76 | $this->addFee('testFeeOne'); 77 | $this->addFee('testFeeTwo', 20); 78 | 79 | $this->assertEquals('$10.00', $this->laracart->getFee('testFeeOne')->getAmount()); 80 | $this->assertEquals('$20.00', $this->laracart->getFee('testFeeTwo')->getAmount()); 81 | 82 | $this->assertEquals(10, $this->laracart->getFee('testFeeOne')->getAmount(false)); 83 | $this->assertEquals(20, $this->laracart->getFee('testFeeTwo')->getAmount(false)); 84 | } 85 | 86 | /** 87 | * Test if we can add multiple fees to the cart. 88 | */ 89 | public function testMultipleFeesTax() 90 | { 91 | $this->addFeeTax('testFeeOne'); 92 | $this->addFeeTax('testFeeTwo', 200); 93 | 94 | $this->assertEquals('$100.00', $this->laracart->getFee('testFeeOne')->getAmount(true, false)); 95 | $this->assertEquals('$121.00', $this->laracart->getFee('testFeeOne')->getAmount(true, true)); 96 | 97 | $this->assertEquals('$242.00', $this->laracart->getFee('testFeeTwo')->getAmount(true, true)); 98 | $this->assertEquals('$200.00', $this->laracart->getFee('testFeeTwo')->getAmount(true, false)); 99 | 100 | $this->assertEquals(121, $this->laracart->getFee('testFeeOne')->getAmount(false, true)); 101 | $this->assertEquals(100, $this->laracart->getFee('testFeeOne')->getAmount(false, false)); 102 | 103 | $this->assertEquals(242, $this->laracart->getFee('testFeeTwo')->getAmount(false, true)); 104 | $this->assertEquals(200, $this->laracart->getFee('testFeeTwo')->getAmount(false, false)); 105 | } 106 | 107 | /** 108 | * Test if we can remove a fee from the cart. 109 | */ 110 | public function testRemoveFee() 111 | { 112 | $this->addFee('testFeeOne'); 113 | $this->assertEquals('$10.00', $this->laracart->getFee('testFeeOne')->getAmount()); 114 | $this->assertEquals(10, $this->laracart->getFee('testFeeOne')->getAmount(false)); 115 | 116 | $this->laracart->removeFee('testFeeOne'); 117 | 118 | $this->assertEquals('$0.00', $this->laracart->getFee('testFeeOne')->getAmount()); 119 | $this->assertEquals(0, $this->laracart->getFee('testFeeOne')->getAmount(false)); 120 | } 121 | 122 | /** 123 | * Test if we can remove all fees from the cart. 124 | */ 125 | public function testRemoveFees() 126 | { 127 | $this->addFee('testFeeOne'); 128 | $this->assertEquals('$10.00', $this->laracart->getFee('testFeeOne')->getAmount()); 129 | $this->assertEquals(10, $this->laracart->getFee('testFeeOne')->getAmount(false)); 130 | 131 | $this->laracart->removeFees(); 132 | 133 | $this->assertTrue(empty($this->laracart->getFees())); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /tests/ItemRelationTest.php: -------------------------------------------------------------------------------- 1 | addItem(); 18 | 19 | $this->assertEmpty($item->itemModel); 20 | 21 | $item->setModel(\LukePOLO\LaraCart\Tests\Models\TestItem::class); 22 | 23 | $this->assertEquals(\LukePOLO\LaraCart\Tests\Models\TestItem::class, $item->getItemModel()); 24 | 25 | $this->assertEquals('itemID', $item->getModel()->id); 26 | 27 | try { 28 | $item->id = 'fail'; 29 | $item->getModel(); 30 | $this->expectException(ModelNotFound::class); 31 | } catch (ModelNotFound $e) { 32 | $this->assertEquals('Could not find the item model for fail', $e->getMessage()); 33 | } 34 | } 35 | 36 | /** 37 | * Test for exception if could not find model. 38 | */ 39 | public function testItemRelationModelException() 40 | { 41 | $item = $this->addItem(); 42 | 43 | try { 44 | $item->setModel('asdfasdf'); 45 | $this->expectException(ModelNotFound::class); 46 | } catch (ModelNotFound $e) { 47 | $this->assertEquals('Could not find relation model', $e->getMessage()); 48 | } 49 | } 50 | 51 | /** 52 | * Testing adding via item id. 53 | */ 54 | public function testAddItemID() 55 | { 56 | $this->laracart->itemModel = \LukePOLO\LaraCart\Tests\Models\TestItem::class; 57 | $this->laracart->item_model_bindings = [ 58 | \LukePOLO\LaraCart\CartItem::ITEM_ID => 'id', 59 | \LukePOLO\LaraCart\CartItem::ITEM_NAME => 'name', 60 | \LukePOLO\LaraCart\CartItem::ITEM_PRICE => 'price', 61 | \LukePOLO\LaraCart\CartItem::ITEM_TAXABLE => 'taxable', 62 | \LukePOLO\LaraCart\CartItem::ITEM_OPTIONS => [ 63 | 'tax', 64 | ], 65 | ]; 66 | 67 | $this->app['config']->set('laracart.item_model', \LukePOLO\LaraCart\Tests\Models\TestItem::class); 68 | 69 | $this->app['config']->set('laracart.item_model_bindings', [ 70 | \LukePOLO\LaraCart\CartItem::ITEM_ID => 'id', 71 | \LukePOLO\LaraCart\CartItem::ITEM_NAME => 'name', 72 | \LukePOLO\LaraCart\CartItem::ITEM_PRICE => 'price', 73 | \LukePOLO\LaraCart\CartItem::ITEM_TAXABLE => 'taxable', 74 | \LukePOLO\LaraCart\CartItem::ITEM_OPTIONS => [ 75 | 'tax', 76 | ], 77 | ]); 78 | 79 | $item = $this->laracart->add('123123'); 80 | 81 | $this->assertEquals($item->getModel()->id, $item->model->id); 82 | 83 | try { 84 | $this->laracart->add('fail'); 85 | } catch (ModelNotFound $e) { 86 | $this->assertEquals('Could not find the item fail', $e->getMessage()); 87 | } 88 | 89 | $this->assertEquals($item, $this->laracart->getItem($item->getHash())); 90 | 91 | $this->assertEquals($item->id, 'itemID'); 92 | $this->assertEquals($item->name, 'Test Item'); 93 | $this->assertEquals($item->qty, 1); 94 | $this->assertEquals($item->tax, '.07'); 95 | $this->assertEquals($item->price, 5000.01); 96 | $this->assertEquals($item->taxable, false); 97 | } 98 | 99 | /** 100 | * Testing adding a item model. 101 | */ 102 | public function testAddItemModel() 103 | { 104 | $this->app['config']->set('laracart.item_model', \LukePOLO\LaraCart\Tests\Models\TestItem::class); 105 | 106 | $this->app['config']->set('laracart.item_model_bindings', [ 107 | \LukePOLO\LaraCart\CartItem::ITEM_ID => 'id', 108 | \LukePOLO\LaraCart\CartItem::ITEM_NAME => 'name', 109 | \LukePOLO\LaraCart\CartItem::ITEM_PRICE => 'price', 110 | \LukePOLO\LaraCart\CartItem::ITEM_TAXABLE => 'taxable', 111 | \LukePOLO\LaraCart\CartItem::ITEM_OPTIONS => [ 112 | 'tax', 113 | ], 114 | ]); 115 | 116 | $item = new \LukePOLO\LaraCart\Tests\Models\TestItem([ 117 | 'price' => 5000.01, 118 | 'taxable' => false, 119 | 'tax' => '.5', 120 | ]); 121 | 122 | $item = $this->laracart->add($item); 123 | 124 | $this->assertEquals($item, $this->laracart->getItem($item->getHash())); 125 | 126 | $this->assertEquals($item->id, 'itemID'); 127 | $this->assertEquals($item->name, 'Test Item'); 128 | $this->assertEquals($item->qty, 1); 129 | $this->assertEquals($item->tax, '.5'); 130 | $this->assertEquals($item->price, 5000.01); 131 | $this->assertEquals($item->taxable, false); 132 | } 133 | 134 | /** 135 | * gtesting multiple item models at once. 136 | */ 137 | public function testAddMultipleItemModel() 138 | { 139 | $this->app['config']->set('laracart.item_model', \LukePOLO\LaraCart\Tests\Models\TestItem::class); 140 | 141 | $this->app['config']->set('laracart.item_model_bindings', [ 142 | \LukePOLO\LaraCart\CartItem::ITEM_ID => 'id', 143 | \LukePOLO\LaraCart\CartItem::ITEM_NAME => 'name', 144 | \LukePOLO\LaraCart\CartItem::ITEM_PRICE => 'price', 145 | \LukePOLO\LaraCart\CartItem::ITEM_TAXABLE => 'taxable', 146 | \LukePOLO\LaraCart\CartItem::ITEM_OPTIONS => [ 147 | 'tax', 148 | ], 149 | ]); 150 | 151 | $item = new \LukePOLO\LaraCart\Tests\Models\TestItem([ 152 | 'price' => 5000.01, 153 | 'taxable' => false, 154 | 'tax' => '.5', 155 | ]); 156 | 157 | $this->laracart->add($item); 158 | $item = $this->laracart->add($item); 159 | 160 | $this->assertEquals(2, $this->laracart->getItem($item->getHash())->qty); 161 | } 162 | 163 | /** 164 | * Testing adding a model to a line item. 165 | */ 166 | public function testAddItemModelLine() 167 | { 168 | $this->app['config']->set('laracart.item_model', \LukePOLO\LaraCart\Tests\Models\TestItem::class); 169 | 170 | $this->app['config']->set('laracart.item_model_bindings', [ 171 | \LukePOLO\LaraCart\CartItem::ITEM_ID => 'id', 172 | \LukePOLO\LaraCart\CartItem::ITEM_NAME => 'name', 173 | \LukePOLO\LaraCart\CartItem::ITEM_PRICE => 'price', 174 | \LukePOLO\LaraCart\CartItem::ITEM_TAXABLE => 'taxable', 175 | \LukePOLO\LaraCart\CartItem::ITEM_OPTIONS => [ 176 | 'tax', 177 | ], 178 | ]); 179 | 180 | $item = new \LukePOLO\LaraCart\Tests\Models\TestItem([ 181 | 'price' => 5000.01, 182 | 'taxable' => false, 183 | 'tax' => '.5', 184 | ]); 185 | 186 | $item = $this->laracart->addLine($item); 187 | 188 | $this->assertEquals($item, $this->laracart->getItem($item->getHash())); 189 | 190 | $this->assertEquals($item->id, 'itemID'); 191 | $this->assertEquals($item->name, 'Test Item'); 192 | $this->assertEquals($item->qty, 1); 193 | $this->assertEquals($item->tax, '.5'); 194 | $this->assertEquals($item->price, 5000.01); 195 | $this->assertEquals($item->taxable, false); 196 | } 197 | 198 | /** 199 | * Testing adding multiple item models per row. 200 | */ 201 | public function testAddMultipleItemModelLine() 202 | { 203 | $this->app['config']->set('laracart.item_model', \LukePOLO\LaraCart\Tests\Models\TestItem::class); 204 | 205 | $this->app['config']->set('laracart.item_model_bindings', [ 206 | \LukePOLO\LaraCart\CartItem::ITEM_ID => 'id', 207 | \LukePOLO\LaraCart\CartItem::ITEM_NAME => 'name', 208 | \LukePOLO\LaraCart\CartItem::ITEM_PRICE => 'price', 209 | \LukePOLO\LaraCart\CartItem::ITEM_TAXABLE => 'taxable', 210 | \LukePOLO\LaraCart\CartItem::ITEM_OPTIONS => [ 211 | 'tax', 212 | ], 213 | ]); 214 | 215 | $item = new \LukePOLO\LaraCart\Tests\Models\TestItem([ 216 | 'price' => 5000.01, 217 | 'taxable' => false, 218 | 'tax' => '.5', 219 | ]); 220 | 221 | $this->laracart->addLine($item); 222 | $item = $this->laracart->addLine($item); 223 | 224 | $this->assertEquals(1, $this->laracart->getItem($item->getHash())->qty); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /tests/ItemsTest.php: -------------------------------------------------------------------------------- 1 | addItem(); 20 | 21 | Event::assertDispatched('laracart.addItem', function ($e, $item) use ($cartItem) { 22 | return $item === $cartItem; 23 | }); 24 | 25 | $this->assertEquals(1, Event::dispatched('laracart.addItem')->count()); 26 | 27 | $this->addItem(); 28 | 29 | $this->assertEquals(1, $this->laracart->count(false)); 30 | $this->assertEquals(2, $this->laracart->count()); 31 | } 32 | 33 | /** 34 | * Test if we can increment a quantity to an item. 35 | */ 36 | public function testIncrementItem() 37 | { 38 | $item = $this->addItem(); 39 | $itemHash = $item->getHash(); 40 | $this->laracart->increment($itemHash); 41 | 42 | $this->assertEquals(2, $this->laracart->count()); 43 | } 44 | 45 | /** 46 | * Test if we can decrement a quantity to an item. 47 | */ 48 | public function testDecrementItem() 49 | { 50 | $item = $this->addItem(); 51 | $itemHash = $item->getHash(); 52 | $this->laracart->increment($itemHash); 53 | $this->laracart->decrement($itemHash); 54 | 55 | $this->assertEquals(1, $this->laracart->count()); 56 | 57 | $this->laracart->decrement($itemHash); 58 | 59 | $this->assertEquals(0, $this->laracart->count()); 60 | } 61 | 62 | /** 63 | * Test if we can decrement an item with a quantity of 1 (= delete item). 64 | */ 65 | public function testDecrementUniqueItem() 66 | { 67 | $item = $this->addItem(); 68 | $itemHash = $item->getHash(); 69 | $this->laracart->decrement($itemHash); 70 | 71 | $this->assertEquals(null, $this->laracart->getItem($itemHash)); 72 | } 73 | 74 | /** 75 | * Tests when we add multiples of the same item it updates the qty properly. 76 | */ 77 | public function testItemQtyUpdate() 78 | { 79 | $item = $this->addItem(); 80 | $itemHash = $item->getHash(); 81 | $this->addItem(); 82 | $this->addItem(); 83 | $this->addItem(); 84 | $this->addItem(); 85 | $this->addItem(); 86 | $this->addItem(); 87 | 88 | $this->assertEquals(7, $item->qty); 89 | $this->assertEquals($itemHash, $item->getHash()); 90 | 91 | $options = [ 92 | 'a' => 2, 93 | 'b' => 1, 94 | ]; 95 | 96 | $item = $this->addItem(1, 1, false, $options); 97 | $this->addItem(1, 1, false, array_reverse($options)); 98 | 99 | $this->assertEquals(2, $item->qty); 100 | } 101 | 102 | /** 103 | * Test if we can add an line item to the cart. 104 | */ 105 | public function testAddLineItem() 106 | { 107 | $this->addItem(); 108 | $this->addItem(); 109 | 110 | $this->laracart->addLine( 111 | 'itemID', 112 | 'Testing Item', 113 | 1, 114 | '1', 115 | [ 116 | 'b_test' => 'option_1', 117 | 'a_test' => 'option_2', 118 | ] 119 | ); 120 | 121 | $this->assertEquals(2, $this->laracart->count(false)); 122 | $this->assertEquals(3, $this->laracart->count()); 123 | } 124 | 125 | /** 126 | * Test getting an item from the cart. 127 | */ 128 | public function testGetItem() 129 | { 130 | $item = $this->addItem(); 131 | $this->assertEquals($item, $this->laracart->getItem($item->getHash())); 132 | } 133 | 134 | /** 135 | * Test updating the item. 136 | */ 137 | public function testUpdateItem() 138 | { 139 | $item = $this->addItem(); 140 | 141 | Event::fake(); 142 | 143 | $this->laracart->updateItem($item->getHash(), 'qty', 4); 144 | 145 | Event::assertDispatched('laracart.updateItem', function ($e, $eventItem) use ($item) { 146 | return $eventItem['item'] === $item && $eventItem['newHash'] === $item->getHash(); 147 | }); 148 | 149 | $this->assertEquals(1, Event::dispatched('laracart.updateItem')->count()); 150 | $this->assertEquals(4, $item->qty); 151 | } 152 | 153 | /** 154 | * Test getting all the items from the cart. 155 | */ 156 | public function testGetItems() 157 | { 158 | $this->addItem(); 159 | $this->addItem(); 160 | 161 | $items = $this->laracart->getItems(); 162 | 163 | $this->assertCount(1, $items); 164 | 165 | $this->containsOnlyInstancesOf(LukePOLO\LaraCart\CartItem::class, $items); 166 | } 167 | 168 | /** 169 | * Test the price and qty based on the item. 170 | */ 171 | public function testItemPriceAndQty() 172 | { 173 | $item = $this->addItem(3, 10); 174 | 175 | $this->assertEquals(10, $item->getPrice()); 176 | $this->assertEquals(3, $item->qty); 177 | $this->assertEquals(30, $item->subTotal(false)); 178 | $this->assertEquals(32.1, $item->total(false)); 179 | } 180 | 181 | /** 182 | * Test the prices in cents based on the item. 183 | */ 184 | public function testItemPriceInCents() 185 | { 186 | $this->app['config']->set('laracart.prices_in_cents', true); 187 | $item = $this->addItem(3, 1000); 188 | 189 | $this->assertEquals(3210, $item->total(false)); 190 | $this->assertEquals(3000, $item->subTotal(false)); 191 | } 192 | 193 | /** 194 | * Test removing an item from the cart. 195 | */ 196 | public function testRemoveItem() 197 | { 198 | $item = $this->addItem(); 199 | 200 | $this->laracart->removeItem($item->getHash()); 201 | 202 | $this->assertEmpty($this->laracart->getItem($item->getHash())); 203 | } 204 | 205 | /** 206 | * Test seeing a valid and invalid price. 207 | */ 208 | public function testSetPrice() 209 | { 210 | $item = $this->addItem(); 211 | 212 | try { 213 | $item->price = 'a'; 214 | $this->expectException(\LukePOLO\LaraCart\Exceptions\InvalidPrice::class); 215 | } catch (\LukePOLO\LaraCart\Exceptions\InvalidPrice $e) { 216 | $this->assertEquals('The price must be a valid number', $e->getMessage()); 217 | } 218 | } 219 | 220 | /** 221 | * Test seeing a valid and invalid qty. 222 | */ 223 | public function testSetQty() 224 | { 225 | $item = $this->addItem(); 226 | 227 | $item->qty = 3; 228 | $this->assertEquals(3, $item->qty); 229 | 230 | $item->qty = 1.5; 231 | $this->assertEquals(1.5, $item->qty); 232 | 233 | try { 234 | $item->qty = 'a'; 235 | $this->expectException(\LukePOLO\LaraCart\Exceptions\InvalidPrice::class); 236 | } catch (\LukePOLO\LaraCart\Exceptions\InvalidQuantity $e) { 237 | $this->assertEquals('The quantity must be a valid number', $e->getMessage()); 238 | } 239 | 240 | try { 241 | $item->qty = 'a'; 242 | $this->expectException(\LukePOLO\LaraCart\Exceptions\InvalidQuantity::class); 243 | } catch (\LukePOLO\LaraCart\Exceptions\InvalidQuantity $e) { 244 | $this->assertEquals('The quantity must be a valid number', $e->getMessage()); 245 | } 246 | 247 | try { 248 | $item->qty = -1; 249 | $this->expectException(\LukePOLO\LaraCart\Exceptions\InvalidQuantity::class); 250 | } catch (\LukePOLO\LaraCart\Exceptions\InvalidQuantity $e) { 251 | $this->assertEquals('The quantity must be a valid number', $e->getMessage()); 252 | } 253 | } 254 | 255 | /** 256 | * Tests the different taxes on items. 257 | */ 258 | public function testDifferentTaxes() 259 | { 260 | $item = $this->addItem(); 261 | $item2 = $this->addItem(1, 1, true, [ 262 | 'tax' => '.05', 263 | ]); 264 | 265 | $this->assertEquals(true, $item->tax !== $item2->tax); 266 | 267 | $this->assertEquals('0.12', $this->laracart->taxTotal(false)); 268 | } 269 | 270 | /** 271 | * Test that an item can be found by the value of an option. 272 | */ 273 | public function testFindingAnItemByOptionSucceeds() 274 | { 275 | $item1 = $this->addItem(1, 1, true, [ 276 | 'key1' => 'matching', 277 | 'key2' => 'value1', 278 | ]); 279 | 280 | $item2 = $this->addItem(1, 1, true, [ 281 | 'key1' => 'notmatching', 282 | 'key2' => 'value2', 283 | ]); 284 | 285 | $result1 = $this->laracart->find(['key1' => 'matching']); 286 | $result2 = $this->laracart->find(['key2' => 'value2']); 287 | 288 | $this->assertEquals($item1->key1, $result1->key1); 289 | $this->assertEquals($item1->getHash(), $result1->getHash()); 290 | 291 | $this->assertEquals($item2->key2, $result2->key2); 292 | $this->assertEquals($item2->getHash(), $result2->getHash()); 293 | } 294 | 295 | /** 296 | * Test that an item is not found by the value of an option when it does not exist. 297 | */ 298 | public function testFindingAnItemByOptionFails() 299 | { 300 | $this->addItem(1, 1, true, [ 301 | 'key1' => 'notmatching', 302 | ]); 303 | 304 | $this->addItem(1, 1, true, [ 305 | 'key2' => 'notmatching', 306 | ]); 307 | 308 | $this->assertNull($this->laracart->find(['key1' => 'matching'])); 309 | } 310 | 311 | /** 312 | * Test that multiple matching items are found by the value of an option. 313 | */ 314 | public function testFindingAnItemReturnsMultipleMatches() 315 | { 316 | $item1 = $this->addItem(1, 1, true, [ 317 | 'key1' => 'matching', 318 | 'key2' => 'value1', 319 | ]); 320 | 321 | $item2 = $this->addItem(1, 1, true, [ 322 | 'key1' => 'matching', 323 | 'key2' => 'value2', 324 | ]); 325 | 326 | $item3 = $this->addItem(1, 1, true, [ 327 | 'key1' => 'nomatch', 328 | ]); 329 | 330 | $results = $this->laracart->find(['key1' => 'matching']); 331 | 332 | $this->assertCount(2, $results); 333 | $this->assertEquals($item1->key1, $results[0]->key1); 334 | $this->assertEquals($item1->getHash(), $results[0]->getHash()); 335 | $this->assertEquals($item2->key1, $results[1]->key1); 336 | $this->assertEquals($item2->getHash(), $results[1]->getHash()); 337 | } 338 | 339 | /** 340 | * Test that an multiple matching items are found by the value of an option. 341 | */ 342 | public function testFindingAnItemOnAnEmptyCartReturnsNoMatches() 343 | { 344 | $this->assertNull($this->laracart->find(['key1' => 'matching'])); 345 | } 346 | 347 | /** 348 | * Test an item is returned when finding multiple criteria. 349 | */ 350 | public function testFindingAnItemWithMultipleCriteria() 351 | { 352 | $item1 = $this->addItem(1, 1, true, [ 353 | 'key1' => 'value1', 354 | 'key2' => 'value2', 355 | ]); 356 | 357 | $item2 = $this->addItem(1, 1, true, [ 358 | 'key1' => 'value1', 359 | 'key2' => 'value3', 360 | ]); 361 | 362 | $result1 = $this->laracart->find(['key1' => 'value1', 'key2' => 'value2']); 363 | $result2 = $this->laracart->find(['key1' => 'value1', 'key2' => 'value3']); 364 | 365 | $this->assertEquals($item1->key2, $result1->key2); 366 | $this->assertEquals($item1->getHash(), $result1->getHash()); 367 | 368 | $this->assertEquals($item2->key2, $result2->key2); 369 | $this->assertEquals($item2->getHash(), $result2->getHash()); 370 | 371 | $this->assertNull($this->laracart->find(['key1' => 'value2', 'key2' => 'value2'])); 372 | 373 | $result3 = $this->laracart->find(['key1' => 'value1']); 374 | $this->assertCount(2, $result3); 375 | $this->assertEquals($item1->getHash(), $result3[0]->getHash()); 376 | $this->assertEquals($item2->getHash(), $result3[1]->getHash()); 377 | } 378 | 379 | /** 380 | * Test an item is found searching by name. 381 | */ 382 | public function testFindingAnItemByName() 383 | { 384 | $item1 = $this->laracart->add('item1234', 'My Item', 1, 2, [ 385 | 'key1' => 'value1', 386 | 'key2' => 'value2', 387 | ]); 388 | 389 | $item2 = $this->addItem(1, 1, true, [ 390 | 'key1' => 'value1', 391 | 'key2' => 'value3', 392 | ]); 393 | 394 | $result = $this->laracart->find(['name' => 'My Item']); 395 | 396 | $this->assertEquals($item1->name, $result->name); 397 | $this->assertEquals($item1->getHash(), $result->getHash()); 398 | 399 | $this->assertNull($this->laracart->find(['name' => 'My Item', 'key2' => 'nomatch'])); 400 | } 401 | 402 | public function testQtyUpdate() 403 | { 404 | $item = $this->addItem(); 405 | 406 | $this->assertEquals(1, $this->laracart->count(false)); 407 | 408 | $this->laracart->updateItem($item->getHash(), 'qty', 0); 409 | 410 | $this->assertEquals(0, $this->laracart->count(false)); 411 | } 412 | 413 | public function testfindItemById() 414 | { 415 | $item = $this->addItem(); 416 | $item->id = 123; 417 | 418 | $this->assertEquals($item, $this->laracart->find([ 419 | 'id' => 123, 420 | ])); 421 | } 422 | 423 | public function testTaxationTotal() 424 | { 425 | $this->addItem(1, 8.33, true, [ 426 | 'tax' => '.2', 427 | ]); 428 | 429 | $this->assertEquals(10.00, $this->laracart->total(false)); 430 | 431 | $this->addItem(1, 8.33, true, [ 432 | 'tax' => '.2', 433 | ]); 434 | 435 | $this->assertEquals(16.66, $this->laracart->netTotal(false)); 436 | 437 | $this->assertEquals(20.00, $this->laracart->total(false)); 438 | 439 | $this->addItem(1, 8.33, true, [ 440 | 'tax' => '.2', 441 | ]); 442 | 443 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed( 444 | '8.33 OFF', 445 | 8.33 446 | ); 447 | 448 | $this->laracart->addCoupon($fixedCoupon); 449 | 450 | $this->assertEquals(20.00, $this->laracart->total(false)); 451 | } 452 | 453 | public function testSeparateTaxationTotal() 454 | { 455 | $this->addItem(1, 8.33, 1, [ 456 | 'tax' => '.2', 457 | ]); 458 | 459 | $this->addItem(1, 8.33, 1, [ 460 | 'tax' => '.2', 461 | 'some' => 'test', 462 | 'name' => '12313', 463 | ]); 464 | 465 | $this->assertEquals(20.00, $this->laracart->total(false)); 466 | } 467 | } 468 | -------------------------------------------------------------------------------- /tests/LaraCartTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 16 | new \LukePOLO\LaraCart\LaraCart($this->session, $this->events, $this->authManager), 17 | $this->app->make('laracart') 18 | ); 19 | } 20 | 21 | /** 22 | * Test setting the instance. 23 | */ 24 | public function testSetInstance() 25 | { 26 | $this->assertNotEquals( 27 | new \LukePOLO\LaraCart\LaraCart($this->session, $this->events, $this->authManager), 28 | $this->laracart->setInstance('test') 29 | ); 30 | } 31 | 32 | /** 33 | * Test to make sure we get default instance. 34 | */ 35 | public function testGetInstancesDefault() 36 | { 37 | $this->assertEquals('default', $this->laracart->getInstances()[0]); 38 | } 39 | 40 | /** 41 | * Test to make sure we can get instances. 42 | */ 43 | public function testGetInstances() 44 | { 45 | $this->laracart->setInstance('test'); 46 | $this->laracart->setInstance('test'); 47 | $this->laracart->setInstance('test'); 48 | $this->laracart->setInstance('test-2'); 49 | $this->laracart->setInstance('test-3'); 50 | 51 | $this->assertCount(4, $this->laracart->getInstances()); 52 | } 53 | 54 | /** 55 | * Testing the money format function. 56 | */ 57 | public function testFormatMoney() 58 | { 59 | $this->assertEquals('$25.00', $this->laracart->formatMoney('25.00')); 60 | $this->assertEquals('€25.00', $this->laracart->formatMoney('25.00', null, 'EUR')); 61 | $this->assertEquals('25.00', $this->laracart->formatMoney('25.00', null, null, false)); 62 | 63 | $this->assertEquals('$25.56', $this->laracart->formatMoney('25.555')); 64 | $this->assertEquals('$25.54', $this->laracart->formatMoney('25.544')); 65 | } 66 | 67 | /** 68 | * Testing the money format function with the prices_in_cents config setting. 69 | */ 70 | public function testFormatMoneyPricesInCents() 71 | { 72 | $this->app['config']->set('laracart.prices_in_cents', true); 73 | 74 | $this->assertEquals('$25.00', $this->laracart->formatMoney(2500)); 75 | $this->assertEquals('CA$25.00', $this->laracart->formatMoney(2500, null, 'CAD')); 76 | $this->assertEquals(2500, $this->laracart->formatMoney(2500, null, null, false)); 77 | 78 | $this->assertEquals('$25.01', $this->laracart->formatMoney(2500.55)); 79 | $this->assertEquals('$25.00', $this->laracart->formatMoney(2500.44)); 80 | 81 | $this->assertEquals(2501, $this->laracart->formatMoney(2500.55, null, null, false)); 82 | $this->assertEquals(2500, $this->laracart->formatMoney(2500.44, null, null, false)); 83 | } 84 | 85 | /** 86 | * Test getting the attributes from the cart. 87 | */ 88 | public function testGetAttributes() 89 | { 90 | $this->laracart->setAttribute('test1', 1); 91 | $this->laracart->setAttribute('test2', 2); 92 | 93 | $this->assertCount(2, $attributes = $this->laracart->getAttributes()); 94 | 95 | $this->assertEquals(1, $attributes['test1']); 96 | $this->assertEquals(2, $attributes['test2']); 97 | } 98 | 99 | /** 100 | * Test removing attributes from the cart. 101 | */ 102 | public function testRemoveAttribute() 103 | { 104 | $this->laracart->setAttribute('test1', 1); 105 | 106 | $this->assertEquals(1, $this->laracart->getAttribute('test1')); 107 | 108 | $this->laracart->removeAttribute('test1'); 109 | 110 | $this->assertNull($this->laracart->getAttribute('test1')); 111 | } 112 | 113 | /** 114 | * Testing if the item count matches. 115 | */ 116 | public function testCount() 117 | { 118 | $this->addItem(2); 119 | 120 | $this->assertEquals(2, $this->laracart->count()); 121 | $this->assertEquals(1, $this->laracart->count(false)); 122 | } 123 | 124 | /** 125 | * Makes sure that when we empty the cart it deletes all items. 126 | */ 127 | public function testEmptyCart() 128 | { 129 | $this->addItem(); 130 | 131 | $this->laracart->setAttribute('test', 1); 132 | 133 | $this->laracart->emptyCart(); 134 | 135 | $this->assertEquals(1, $this->laracart->getAttribute('test')); 136 | $this->assertEquals(0, $this->laracart->count()); 137 | } 138 | 139 | /** 140 | * Test destroying the cart rather than just emptying it. 141 | */ 142 | public function testDestroyCart() 143 | { 144 | $this->addItem(); 145 | 146 | $this->laracart->setAttribute('test', 1); 147 | 148 | $this->laracart->destroyCart(); 149 | 150 | $this->assertEquals(null, $this->laracart->getAttribute('test')); 151 | $this->assertEquals(0, $this->laracart->count()); 152 | } 153 | 154 | /** 155 | * Testing to make sure if we switch carts and destroy it destroys the proper cart. 156 | */ 157 | public function testDestroyOtherCart() 158 | { 159 | $this->addItem(); 160 | 161 | $this->laracart->setInstance('test'); 162 | 163 | $this->addItem(); 164 | 165 | $cart = $this->laracart->get('test'); 166 | 167 | $this->assertEquals(1, $cart->count()); 168 | 169 | $this->laracart->destroyCart(); 170 | 171 | $cart = $this->laracart->get('test'); 172 | 173 | $this->assertEquals(0, $cart->count()); 174 | 175 | $cart = $this->laracart->get(); 176 | 177 | $this->assertEquals(1, $cart->count()); 178 | } 179 | 180 | /** 181 | * Tests if generating a new hash when we change an option. 182 | */ 183 | public function testGeneratingHashes() 184 | { 185 | $item = $this->addItem(); 186 | 187 | $prevHash = $item->getHash(); 188 | 189 | $item->name = 'NEW NAME'; 190 | 191 | $this->assertNotEquals($prevHash, $item->getHash()); 192 | } 193 | 194 | /** 195 | * Tests if generating a same hash when we change an excluded option. 196 | */ 197 | public function testGeneratingHashesExistConfig() 198 | { 199 | $this->app['config']->set('laracart.exclude_from_hash', ['some_option']); 200 | 201 | $item = $this->addItem(1, 1, true, ['some_option' => 'some value']); 202 | 203 | $prevHash = $item->getHash(); 204 | 205 | $item->some_option = 'NEW VALUE'; 206 | 207 | $this->assertEquals($prevHash, $item->getHash()); 208 | } 209 | 210 | /** 211 | * Tests the facade. 212 | */ 213 | public function getFacadeName() 214 | { 215 | $facade = new \LukePOLO\LaraCart\Facades\LaraCart(); 216 | $this->assertEquals('laracart', $facade::getFacadeAccessor()); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /tests/LaraCartTestTrait.php: -------------------------------------------------------------------------------- 1 | laracart = new \LukePOLO\LaraCart\LaraCart($this->session, $this->events, $this->authManager); 25 | } 26 | 27 | /** 28 | * Default tax setup. 29 | * 30 | * @param $app 31 | */ 32 | protected function getEnvironmentSetUp($app) 33 | { 34 | $this->session = $app['session']; 35 | $this->events = $app['events']; 36 | $this->authManager = $app['auth']; 37 | 38 | $app['config']->set('database.default', 'testing'); 39 | 40 | // Setup default database to use sqlite :memory: 41 | $app['config']->set('laracart.tax', '.07'); 42 | } 43 | 44 | /** 45 | * Sets the package providers. 46 | * 47 | * @param $app 48 | * 49 | * @return array 50 | */ 51 | protected function getPackageProviders($app) 52 | { 53 | return ['\LukePOLO\LaraCart\LaraCartServiceProvider']; 54 | } 55 | 56 | /** 57 | * Easy way to add an item for many tests. 58 | * 59 | * @param int $qty 60 | * @param int $price 61 | * @param bool $taxable 62 | * @param array $options 63 | * 64 | * @return mixed 65 | */ 66 | private function addItem($qty = 1, $price = 1, $taxable = true, $options = []) 67 | { 68 | if (empty($options)) { 69 | $options = [ 70 | 'b_test' => 'option_1', 71 | 'a_test' => 'option_2', 72 | ]; 73 | } 74 | 75 | return $this->laracart->add( 76 | 'itemID', 77 | 'Testing Item', 78 | $qty, 79 | $price, 80 | $options, 81 | $taxable 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/MagicFunctionsTest.php: -------------------------------------------------------------------------------- 1 | addItem(); 18 | 19 | $this->assertEquals('option_1', $item->b_test); 20 | } 21 | 22 | /** 23 | * Test the magic method set. 24 | */ 25 | public function testSet() 26 | { 27 | $item = $this->addItem(); 28 | 29 | $item->test_option = 123; 30 | 31 | $this->assertEquals(123, $item->test_option); 32 | 33 | try { 34 | $item->tax = 'not_a_number'; 35 | $this->expectException(InvalidTaxableValue::class); 36 | } catch (InvalidTaxableValue $e) { 37 | $this->assertEquals('The tax must be a number', $e->getMessage()); 38 | } 39 | 40 | try { 41 | $item->taxable = 123123; 42 | $this->expectException(InvalidTaxableValue::class); 43 | } catch (InvalidTaxableValue $e) { 44 | $this->assertEquals('The taxable option must be a boolean', $e->getMessage()); 45 | } 46 | 47 | $item->taxable = 1; 48 | $item->taxable = 0; 49 | } 50 | 51 | /** 52 | * Test the magic method isset. 53 | */ 54 | public function testIsset() 55 | { 56 | $item = $this->addItem(); 57 | 58 | $this->assertEquals(true, isset($item->b_test)); 59 | $this->assertEquals(false, isset($item->testtestestestsetset)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Models/TestItem.php: -------------------------------------------------------------------------------- 1 | cart_sessoin_id = $this->getAttribute('cart_sessoin_id'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/SubItemsTest.php: -------------------------------------------------------------------------------- 1 | addItem(); 16 | 17 | $subItem = $item->addSubItem([ 18 | 'size' => 'XXL', 19 | 'price' => 2.50, 20 | ]); 21 | 22 | $this->containsOnlyInstancesOf(LukePOLO\LaraCart\CartSubItem::class, $item->subItems); 23 | 24 | $this->assertEquals($subItem, $item->findSubItem($subItem->getHash())); 25 | } 26 | 27 | /** 28 | * Test getting the total from a sub item. 29 | */ 30 | public function testSubItemTotal() 31 | { 32 | $item = $this->addItem(); 33 | 34 | $item->addSubItem([ 35 | 'size' => 'XXL', 36 | 'price' => 2.50, 37 | ]); 38 | 39 | $this->assertEquals(3.50, $item->subTotal(false)); 40 | $this->assertEquals(2.50, $item->subItemsTotal(false)); 41 | } 42 | 43 | /** 44 | * Test the sub items with more sub items. 45 | */ 46 | public function testSubItemItemsTotal() 47 | { 48 | $item = $this->addItem(1, 10, true, [ 49 | 'tax' => .01, 50 | ]); 51 | 52 | $item->addSubItem([ 53 | 'price' => 10, 54 | 'tax' => .01, 55 | 'items' => [ 56 | new \LukePOLO\LaraCart\CartItem('10', 'sub item item', 1, 10, [ 57 | 'tax' => .01, 58 | ]), 59 | ], 60 | ]); 61 | 62 | $this->assertEquals(20, $item->subItemsTotal(false)); 63 | $this->assertEquals(30, $item->subTotal(false)); 64 | $this->assertEquals(.30, $this->laracart->taxTotal(false)); 65 | $this->assertEquals(30.30, $this->laracart->total(false)); 66 | } 67 | 68 | public function testSubItemMultiQtyTaxation() 69 | { 70 | $item = $this->addItem(1, 10, true, [ 71 | 'tax' => .01, 72 | ]); 73 | 74 | $item->addSubItem([ 75 | 'price' => 10, 76 | 'tax' => .01, 77 | 'items' => [ 78 | new \LukePOLO\LaraCart\CartItem('10', 'sub item item', 10, 1, [ 79 | 'tax' => .01, 80 | ]), 81 | ], 82 | ]); 83 | 84 | $this->assertEquals(20, $item->subItemsTotal(false)); 85 | $this->assertEquals(30, $item->subTotal(false)); 86 | $this->assertEquals(.30, $this->laracart->taxTotal(false)); 87 | $this->assertEquals(30.30, $this->laracart->total(false)); 88 | } 89 | 90 | /** 91 | * Testing totals for sub sub items. 92 | */ 93 | public function testSubItemsSubItemsTotal() 94 | { 95 | $item = $this->addItem(1, 11); 96 | 97 | $subItem = new \LukePOLO\LaraCart\CartItem('10', 'sub item item', 1, 2); 98 | 99 | $subItem->addSubItem([ 100 | 'items' => [ 101 | new \LukePOLO\LaraCart\CartItem('10', 'sub item item', 1, 1), 102 | ], 103 | ]); 104 | 105 | $item->addSubItem([ 106 | 'items' => [ 107 | $subItem, 108 | ], 109 | ]); 110 | 111 | $this->assertEquals(3, $item->subItemsTotal(false)); 112 | $this->assertEquals(14, $item->subTotal(false)); 113 | $this->assertEquals(14.98, $item->total(false)); 114 | } 115 | 116 | /** 117 | * Test adding an item on a sub item. 118 | */ 119 | public function testAddSubItemItems() 120 | { 121 | $item = $this->addItem(); 122 | 123 | $subItem = $item->addSubItem([ 124 | 'size' => 'XXL', 125 | 'price' => 2.50, 126 | 'items' => [ 127 | new \LukePOLO\LaraCart\CartItem('itemId', 'test item', 1, 10), 128 | ], 129 | ]); 130 | 131 | $this->containsOnlyInstancesOf(LukePOLO\LaraCart\CartItem::class, $subItem->items); 132 | 133 | $this->assertEquals(12.50, $subItem->subTotal()); 134 | } 135 | 136 | /** 137 | * Test adding an item on a sub item. 138 | */ 139 | public function testAddSubItemItemsWithQty() 140 | { 141 | $item = $this->addItem(); 142 | 143 | $subItem = $item->addSubItem([ 144 | 'size' => 'XXL', 145 | 'price' => 2.50, 146 | 'items' => [ 147 | new \LukePOLO\LaraCart\CartItem('itemId', 'test item', 1, 10), 148 | ], 149 | ]); 150 | 151 | $this->containsOnlyInstancesOf(LukePOLO\LaraCart\CartItem::class, $subItem->items); 152 | 153 | $this->assertEquals(12.50, $subItem->subTotal()); 154 | $this->assertEquals('13.50', $item->subTotal(false)); 155 | 156 | $this->assertEquals('13.50', $this->laracart->subTotal(false)); 157 | 158 | $item->qty = 2; 159 | $this->assertEquals('27.00', $item->subTotal(false)); 160 | $this->assertEquals('27.00', $this->laracart->subTotal(false)); 161 | } 162 | 163 | /** 164 | * Test removing sub items. 165 | */ 166 | public function testRemoveSubItem() 167 | { 168 | $item = $this->addItem(); 169 | 170 | $subItem = $item->addSubItem([ 171 | 'size' => 'XXL', 172 | 'price' => 2.50, 173 | ]); 174 | 175 | $subItemHash = $subItem->getHash(); 176 | 177 | $this->assertEquals($subItem, $item->findSubItem($subItemHash)); 178 | 179 | $item->removeSubItem($subItemHash); 180 | 181 | $this->assertEquals(null, $item->findSubItem($subItemHash)); 182 | } 183 | 184 | /** 185 | * Test to make sure taxable flag is working for total tax. 186 | */ 187 | public function testAddSubItemItemsSubItemsTax() 188 | { 189 | $item = $this->addItem(); 190 | 191 | $item->addSubItem([ 192 | 'size' => 'XXL', 193 | 'price' => 2.50, 194 | 'items' => [ 195 | new \LukePOLO\LaraCart\CartItem('itemId', 'test item', 1, 10, [], false), 196 | ], 197 | ]); 198 | 199 | $this->assertEquals(13.50, $item->subTotal(false)); 200 | 201 | $this->assertEquals('0.25', $this->laracart->taxTotal(false)); 202 | } 203 | 204 | /** 205 | * Test Tax in case the item is not taxed but subItems are taxable. 206 | */ 207 | public function testAddTaxedSubItemsItemUnTaxed() 208 | { 209 | $item = $this->addItem(1, 1, false); 210 | 211 | // 12.50 212 | $item->addSubItem([ 213 | 'size' => 'XXL', 214 | 'price' => 2.50, 215 | 'taxable' => true, 216 | 'items' => [ 217 | new \LukePOLO\LaraCart\CartItem('itemId', 'test item', 1, 10, [], true), 218 | ], 219 | ]); 220 | 221 | $this->assertEquals(13.50, $item->subTotal(false)); 222 | $this->assertEquals(.88, $this->laracart->taxTotal(false)); 223 | } 224 | 225 | /** 226 | * Test Tax in case the sub sub item is untaxed but sub item is taxed. 227 | */ 228 | public function testAddTaxedSubSubItemUntaxedSubItemTaxed() 229 | { 230 | $item = $this->addItem(1, 1, true); 231 | 232 | $subItem = new \LukePOLO\LaraCart\CartItem('itemId', 'test sub item', 1, 10, [], true); 233 | 234 | $subItem->addSubItem([ 235 | 'items' => [ 236 | // not taxable 237 | new \LukePOLO\LaraCart\CartItem('itemId', 'test sub sub item', 1, 10, [], false), 238 | ], 239 | ]); 240 | 241 | $item->addSubItem([ 242 | 'items' => [ 243 | $subItem, 244 | ], 245 | ]); 246 | 247 | $this->assertEquals(21.00, $item->subTotal(false)); 248 | $this->assertEquals(0.77, $this->laracart->taxTotal(false)); 249 | } 250 | 251 | public function testSearchSubItems() 252 | { 253 | $item = $this->addItem(2, 2, false); 254 | 255 | $subItem = $item->addSubItem([ 256 | 'size' => 'XXL', 257 | 'price' => 2.50, 258 | 'taxable' => true, 259 | 'items' => [ 260 | new \LukePOLO\LaraCart\CartItem('itemId', 'test item', 1, 10, [ 261 | 'amItem' => true, 262 | ], true), 263 | ], 264 | ]); 265 | 266 | $this->assertCount(0, $item->searchForSubItem(['size' => 'XL'])); 267 | 268 | $itemsFound = $item->searchForSubItem(['size' => 'XXL']); 269 | 270 | $this->assertCount(1, $itemsFound); 271 | 272 | $itemFound = $itemsFound[0]; 273 | 274 | $this->assertEquals($subItem->getHash(), $itemFound->getHash()); 275 | $this->assertEquals($subItem->size, $itemFound->size); 276 | } 277 | 278 | public function testDefaultTaxOnSubItem() 279 | { 280 | $item = $this->addItem(1, 0); 281 | 282 | $item->addSubItem([ 283 | 'size' => 'XXL', 284 | 'price' => 10.00, 285 | ]); 286 | 287 | $this->assertEquals(0.7, $this->laracart->taxTotal(false)); 288 | } 289 | 290 | public function testDifferentTaxtionsOnSubItems() 291 | { 292 | $item = $this->addItem(1, 10, true, [ 293 | 'tax' => .01, 294 | ]); 295 | 296 | $item->addSubItem([ 297 | 'size' => 'XXL', 298 | 'price' => 10.00, 299 | 'taxable' => true, 300 | 'tax' => .02, 301 | ]); 302 | 303 | $this->assertEquals(0.30, $this->laracart->taxTotal(false)); 304 | } 305 | 306 | public function testTaxSumary() 307 | { 308 | $item = $this->addItem(1, 10, true, [ 309 | 'tax' => .01, 310 | ]); 311 | 312 | $item->addSubItem([ 313 | 'size' => 'XXL', 314 | 'price' => 10.00, 315 | 'taxable' => true, 316 | 'tax' => .02, 317 | ]); 318 | 319 | $this->assertEquals([ 320 | '0.01' => .10, 321 | '0.02' => .20, 322 | ], $item->taxSummary()); 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /tests/TotalsTest.php: -------------------------------------------------------------------------------- 1 | addItem(1, 10); 16 | 17 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed( 18 | '10OFF', 19 | 10 20 | ); 21 | 22 | $this->laracart->addCoupon($fixedCoupon); 23 | 24 | $this->assertEquals('$10.00', $this->laracart->discountTotal()); 25 | $this->assertEquals(10, $this->laracart->discountTotal(false)); 26 | 27 | $this->assertEquals(0, $this->laracart->total(false)); 28 | } 29 | 30 | /** 31 | * Test total discounts when using the pricing_in_cents config setting. 32 | */ 33 | public function testdiscountTotalInCents() 34 | { 35 | $this->app['config']->set('laracart.prices_in_cents', true); 36 | $this->addItem(1, 1000); 37 | 38 | $fixedCoupon = new LukePOLO\LaraCart\Coupons\Fixed( 39 | '10OFF', 40 | 1000 41 | ); 42 | 43 | $this->laracart->addCoupon($fixedCoupon); 44 | 45 | $this->assertEquals('$10.00', $this->laracart->discountTotal()); 46 | $this->assertEquals(1000, $this->laracart->discountTotal(false)); 47 | 48 | $this->assertEquals(0, $this->laracart->total(false)); 49 | } 50 | 51 | /** 52 | * Test total taxes. 53 | */ 54 | public function testTaxTotal() 55 | { 56 | $this->addItem(); 57 | 58 | $this->assertEquals('$0.07', $this->laracart->taxTotal()); 59 | $this->assertEquals('0.07', $this->laracart->taxTotal(false)); 60 | } 61 | 62 | /** 63 | * Test total taxes when using the pricing_in_cents config setting. 64 | */ 65 | public function testTaxTotalInCents() 66 | { 67 | $this->app['config']->set('laracart.prices_in_cents', true); 68 | $this->addItem(1, 100); 69 | 70 | $this->assertEquals('$0.07', $this->laracart->taxTotal()); 71 | $this->assertEquals(7, $this->laracart->taxTotal(false)); 72 | } 73 | 74 | /** 75 | * Test getting all the fees. 76 | */ 77 | public function testFeeTotals() 78 | { 79 | $this->laracart->addFee('test', 5); 80 | $this->laracart->addFee('test_2', 20); 81 | 82 | $this->assertEquals('$25.00', $this->laracart->feeSubTotal()); 83 | $this->assertEquals(25, $this->laracart->feeSubTotal(false)); 84 | } 85 | 86 | /** 87 | * Test getting a sub total (without tax). 88 | */ 89 | public function testSubTotal() 90 | { 91 | $item = $this->addItem(1, 24); 92 | 93 | $this->assertEquals('$24.00', $this->laracart->subTotal()); 94 | $this->assertEquals('24.00', $this->laracart->subTotal(false)); 95 | 96 | $item->qty = 5; 97 | 98 | $this->assertEquals('120.00', $this->laracart->subTotal(false)); 99 | } 100 | 101 | /** 102 | * Test getting the final total (with tax). 103 | */ 104 | public function testTotal() 105 | { 106 | $this->addItem(); 107 | 108 | $this->assertEquals('$1.07', $this->laracart->total()); 109 | $this->assertEquals('1.07', $this->laracart->total(false)); 110 | } 111 | 112 | /** 113 | * Test getting the final total (with tax) when using the pricing_in_cents config setting. 114 | */ 115 | public function testTotalInCents() 116 | { 117 | $this->app['config']->set('laracart.prices_in_cents', true); 118 | $this->addItem(1, 100); 119 | 120 | $this->assertEquals('$1.07', $this->laracart->total()); 121 | $this->assertEquals(107, $this->laracart->total(false)); 122 | } 123 | 124 | /** 125 | * Test the taxable fees total. 126 | */ 127 | public function testTaxableFees() 128 | { 129 | $this->app['config']->set('laracart.fees_taxable', true); 130 | $this->laracart->addFee('test_2', 1, true, ['tax' => 0.07]); 131 | 132 | $this->assertEquals(1, $this->laracart->feeSubTotal(false)); 133 | 134 | $this->assertEquals('0.07', $this->laracart->taxTotal(false)); 135 | } 136 | 137 | /** 138 | * Test making sure items are taxable and not taxable. 139 | */ 140 | public function testTaxableItems() 141 | { 142 | $this->addItem(); 143 | $item = $this->addItem(1, 2, false); 144 | 145 | // only 1 dollar is taxable! 146 | $this->assertEquals('3.07', $this->laracart->total(false)); 147 | 148 | $item->qty = 5; 149 | 150 | // 3 * 5 = 15 - 5 = 10 , only 10 is taxable 151 | 152 | // only 5 dollar is taxable! 153 | $this->assertEquals('11.00', $this->laracart->subTotal(false)); 154 | $this->assertEquals('11.07', $this->laracart->total(false)); 155 | } 156 | 157 | /** 158 | * Test taxable item with taxable fees. 159 | */ 160 | public function testTotalTaxableItemTaxableFees() 161 | { 162 | $tax = .10; 163 | $priceItem = 10; 164 | $this->addItem(1, $priceItem, true, ['tax' => $tax]); 165 | $this->assertEquals(11, $this->laracart->total(false)); 166 | 167 | $this->app['config']->set('laracart.fees_taxable', true); 168 | $fee = 10; 169 | $this->laracart->addFee('test', $fee, true, ['tax' => $tax]); 170 | 171 | $this->assertEquals($priceItem, $this->laracart->feeSubTotal(false)); 172 | $this->assertEquals($priceItem, $this->laracart->subTotal(false)); 173 | $this->assertEquals($priceItem + $fee, $this->laracart->netTotal(false)); 174 | $taxTotal = ($priceItem * .10) + ($fee * .10); 175 | $this->assertEquals($taxTotal, $this->laracart->taxTotal(false)); 176 | $this->assertEquals($priceItem + $fee + $taxTotal, $this->laracart->total(false)); 177 | } 178 | 179 | /** 180 | * Test NOT taxable item with taxable fees. 181 | */ 182 | public function testTotalNotTaxableItemTaxableFees() 183 | { 184 | $this->app['config']->set('laracart.fees_taxable', true); 185 | 186 | $tax = .20; 187 | $priceItem = 5; 188 | $priceFee = 2; 189 | 190 | $this->addItem(1, $priceItem, false); 191 | $this->laracart->addFee('test', $priceFee, true, ['tax' => $tax]); 192 | 193 | $this->assertEquals('2.00', $this->laracart->feeSubTotal(false, true)); 194 | $this->assertEquals('5.00', $this->laracart->subTotal(false, true)); 195 | $this->assertEquals('7.40', $this->laracart->total(false)); 196 | } 197 | 198 | /** 199 | * Test taxable item with NOT taxable fees. 200 | */ 201 | public function testTotalTaxableItemNotTaxableFees() 202 | { 203 | $tax = .20; 204 | $priceItem = 5; 205 | $priceFee = 2; 206 | 207 | $item = $this->addItem(1, $priceItem, true, ['tax' => $tax]); 208 | $this->laracart->addFee('test', $priceFee, false); 209 | 210 | $this->assertEquals('2.00', $this->laracart->feeSubTotal(false)); 211 | $this->assertEquals('5.00', $this->laracart->subTotal(false)); 212 | $this->assertEquals('8.00', $this->laracart->total(false)); 213 | } 214 | 215 | /** 216 | * Test NOT taxable item with NOT taxable fees. 217 | */ 218 | public function testTotalNotTaxableItemNotTaxableFees() 219 | { 220 | $tax = .20; 221 | $priceItem = 5; 222 | $priceFee = 2; 223 | 224 | $item = $this->addItem(1, $priceItem, false); 225 | $this->laracart->addFee('test', $priceFee, false); 226 | 227 | $this->assertEquals('2.00', $this->laracart->feeSubTotal(false)); 228 | $this->assertEquals('5.00', $this->laracart->subTotal(false)); 229 | $this->assertEquals('7.00', $this->laracart->total(false)); 230 | } 231 | 232 | /** 233 | * Test NOT taxable item with taxable fees. 234 | */ 235 | public function testTotalDifferentTaxItemAndFees() 236 | { 237 | $this->app['config']->set('laracart.fees_taxable', true); 238 | $taxItem = .20; 239 | $taxFee = .07; 240 | $priceItem = 5; 241 | $priceFee = 2; 242 | 243 | $this->addItem(1, $priceItem, true, ['tax' => $taxItem]); 244 | 245 | $this->assertEquals('5.00', $this->laracart->subTotal(false)); 246 | $this->assertEquals('6.00', $this->laracart->total(false)); 247 | 248 | $this->laracart->addFee('test', $priceFee, true, ['tax' => $taxFee]); 249 | $this->assertEquals('2.00', $this->laracart->feeSubTotal(false)); 250 | $this->assertEquals('5.00', $this->laracart->subTotal(false)); 251 | $this->assertEquals('7.00', $this->laracart->netTotal(false)); 252 | $this->assertEquals('8.14', $this->laracart->total(false)); 253 | } 254 | 255 | public function testActivateAndDeactivate() 256 | { 257 | $item = $this->addItem(); 258 | 259 | $this->assertEquals('1.07', $this->laracart->total(false)); 260 | 261 | $item->disable(); 262 | 263 | $this->assertEquals(0, $this->laracart->subTotal(false)); 264 | 265 | $item->enable(); 266 | 267 | $this->assertEquals('1.07', $this->laracart->total(false)); 268 | } 269 | 270 | public function testTotalWithoutTaxableFees() 271 | { 272 | $this->addItem(5); 273 | 274 | $this->assertEquals('5.00', $this->laracart->subTotal(false)); 275 | $this->assertEquals('5.35', $this->laracart->total(false)); 276 | 277 | $this->laracart->addFee('test', 1); 278 | 279 | $this->assertEquals('6.00', $this->laracart->netTotal(false)); 280 | $this->assertEquals('6.35', $this->laracart->total(false)); 281 | } 282 | 283 | public function testTaxTotalWithDiscounts() 284 | { 285 | $this->laracart->add(1, 'Test Product', 1, 100, ['tax' => 0.21]); 286 | 287 | $coupon = new LukePOLO\LaraCart\Coupons\Percentage('test', 0.05, [ 288 | 'name' => '5% off', 289 | 'description' => '5% off test', 290 | ]); 291 | 292 | $this->laracart->addCoupon($coupon); 293 | 294 | $this->assertEquals(100, $this->laracart->subTotal(false)); 295 | $this->assertEquals(5, $this->laracart->discountTotal(false)); 296 | $this->assertEquals(19.95, $this->laracart->taxTotal(false)); 297 | $this->assertEquals(114.95, $this->laracart->total(false)); 298 | } 299 | 300 | public function testDoubleDiscounts() 301 | { 302 | $item = $this->laracart->add(1, 'Test Product', 1, 100, ['tax' => 0.21]); 303 | 304 | $coupon = new LukePOLO\LaraCart\Coupons\Percentage('test', 0.05, [ 305 | 'name' => '5% off', 306 | 'description' => '5% off test', 307 | ]); 308 | 309 | $this->laracart->addCoupon($coupon); 310 | $coupon->setDiscountOnItem($item); 311 | 312 | $this->assertEquals(100, $this->laracart->subTotal(false)); 313 | $this->assertEquals(5, $this->laracart->discountTotal(false)); 314 | $this->assertEquals(95, $this->laracart->netTotal(false)); 315 | $this->assertEquals(19.95, $this->laracart->taxTotal(false)); 316 | $this->assertEquals(114.95, $this->laracart->total(false)); 317 | } 318 | 319 | public function testTaxationOnCoupons() 320 | { 321 | // Add to cart 322 | $this->laracart->add( 323 | 1, 324 | 'test', 325 | 52, 326 | 107.44, 327 | [ 328 | 'tax' => 0.21, 329 | ] 330 | ); 331 | 332 | $this->assertEquals(5586.88, $this->laracart->subTotal(false)); 333 | $this->assertEquals(0, $this->laracart->discountTotal(false)); 334 | $this->assertEquals(1173.24, $this->laracart->taxTotal(false)); 335 | $this->assertEquals(6760.00, $this->laracart->total(false)); 336 | 337 | // Test discount % 338 | $coupon = new LukePOLO\LaraCart\Coupons\Percentage('7.5%', 0.075); 339 | $this->laracart->addCoupon($coupon); 340 | 341 | $this->assertEquals(5586.88, $this->laracart->subTotal(false)); 342 | $this->assertEquals(419.12, $this->laracart->discountTotal(false)); 343 | $this->assertEquals(1085.23, $this->laracart->taxTotal(false)); 344 | $this->assertEquals(6253.00, $this->laracart->total(false)); 345 | 346 | $this->laracart->removeCoupons(); 347 | 348 | // Test discount fixed 349 | $coupon = new LukePOLO\LaraCart\Coupons\Fixed('100 euro', 100); 350 | $this->laracart->addCoupon($coupon); 351 | 352 | $this->assertEquals(5586.88, $this->laracart->subTotal(false)); 353 | $this->assertEquals(100, $this->laracart->discountTotal(false)); 354 | $this->assertEquals(1152.24, $this->laracart->taxTotal(false)); 355 | $this->assertEquals(6639.00, $this->laracart->total(false)); 356 | } 357 | 358 | public function testBasicTotalsWithItemTax() 359 | { 360 | $this->app['config']->set('laracart.tax', .19); 361 | 362 | /* @var \LukePOLO\LaraCart\CartItem $item */ 363 | $this->laracart->add( 364 | 1, 365 | 'Product with 19% Tax', 366 | 1, 367 | 100, 368 | [ 369 | \LukePOLO\LaraCart\CartItem::ITEM_TAX => .19, 370 | ] 371 | ); 372 | 373 | $this->assertEquals(100, $this->laracart->subTotal(false)); 374 | $this->assertEquals(0, $this->laracart->discountTotal(false)); 375 | $this->assertEquals(19, $this->laracart->taxTotal(false)); 376 | $this->assertEquals(119, $this->laracart->total(false)); 377 | } 378 | 379 | public function testDiscountsOnMultiQtyItems() 380 | { 381 | $this->laracart->emptyCart(); 382 | $this->laracart->destroyCart(); 383 | 384 | $item = $this->laracart->add(123, 'T-Shirt', 2, 100, ['tax' => .2], true); 385 | 386 | $coupon = new \LukePOLO\LaraCart\Coupons\Percentage('10%OFF', 0.10); 387 | $this->laracart->addCoupon($coupon); 388 | $coupon->setDiscountOnItem($item); 389 | 390 | $this->assertEquals($item->getDiscount(false), 20); 391 | $this->assertEquals($this->laracart->subTotal(false), 200); 392 | $this->assertEquals($this->laracart->discountTotal(false), 20); 393 | 394 | $this->assertEquals($this->laracart->taxTotal(false), 36); 395 | $this->assertEquals($this->laracart->total(false), 216); 396 | } 397 | 398 | /** 399 | * Test round of prices. Only the total value should be rounded. 400 | */ 401 | public function testRoundOnlyTotalValue() 402 | { 403 | $item = $this->addItem(); 404 | $item->addSubItem([ 405 | 'description' => 'First item', 406 | 'price' => 8.40336, 407 | 'qty' => 1, 408 | ]); 409 | 410 | $item->addSubItem([ 411 | 'description' => 'Second item', 412 | 'price' => 4.20168, 413 | 'qty' => 1, 414 | ]); 415 | $this->assertEquals(13.61, $this->laracart->subTotal(false)); 416 | } 417 | 418 | public function testCartTaxSumary() 419 | { 420 | $this->app['config']->set('laracart.fees_taxable', true); 421 | $item = $this->addItem(1, 10, true, [ 422 | 'tax' => .01, 423 | ]); 424 | 425 | $item->addSubItem([ 426 | 'size' => 'XXL', 427 | 'price' => 10.00, 428 | 'taxable' => true, 429 | 'tax' => .02, 430 | ]); 431 | 432 | $item = $this->addItem(1, 12, true, [ 433 | 'tax' => .01, 434 | ]); 435 | 436 | $item->addSubItem([ 437 | 'size' => 'XXL', 438 | 'price' => 10.00, 439 | 'taxable' => true, 440 | 'tax' => .02, 441 | ]); 442 | 443 | $this->laracart->addFee( 444 | 'cart fee', 445 | 5.00, 446 | true, 447 | [ 448 | 'tax' => .03, 449 | ] 450 | ); 451 | 452 | $this->assertEquals([ 453 | '0.01' => .22, 454 | '0.02' => .40, 455 | '0.03' => .15, 456 | ], $this->laracart->taxSummary()); 457 | } 458 | 459 | public function testQtyOnSubItems() 460 | { 461 | $item = $this->addItem(1, 0); 462 | 463 | $item->addSubItem([ 464 | 'description' => 'Ticket: Erwachsener', 465 | 'price' => 18.48739, 466 | 'qty' => 2, 467 | 'tax' => .19, 468 | ]); 469 | 470 | $this->assertEquals(40.49, $this->laracart->total(false)); 471 | } 472 | 473 | public function testSubTotalTaxRounding() 474 | { 475 | $item = $this->addItem(1, 0); 476 | 477 | $item->addSubItem([ 478 | 'description' => 'Ticket: Erwachsener', 479 | 'price' => 18.48739, 480 | 'qty' => 1, 481 | 'tax' => .19, 482 | ]); 483 | // 18.48739 + (18.48739 *.19) = 21.9999941 484 | 485 | $item->addSubItem([ 486 | 'description' => 'Ticket: Ermäßigt', 487 | 'price' => 16.80672, 488 | 'qty' => 1, 489 | 'tax' => .19, 490 | ]); 491 | 492 | // 16.80672 + (16.80672 *.19) = 19.9999968 493 | 494 | // 21.9999941 + 19.9999968 = 41.9999909 495 | 496 | $this->assertEquals(42.00, $this->laracart->total(false)); 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /upgrade-1.0-2.0.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide from 1.0 -> 2.0 2 | 3 | ## Breaking Changes 4 | 5 | ### Cart Item 6 | 7 | * Removed `price` , I suggest using `subTotal` instead. 8 | * Removed `netTotal` -- probably will put back in 9 | 10 | * `addSubItem(array $subItem, $autoUpdate = true)` -> `addSubItem(array $subItem)` 11 | * `subTotal($format = true, $withDiscount = true, $taxedItemsOnly = false, $withTax = false)` -> `subTotal($format = true)` 12 | * `subItemsTotal($format = true, $taxedItemsOnly = false, $withTax = false)` -> `subItemsTotal($format = true)` 13 | * `tax($amountNotTaxable = 0, $grossTax = true, $rounded = false, $withDiscount = true)` -> `tax($format = true)` 14 | 15 | ### Cart Sub Item 16 | 17 | * Removed `price` , I suggest using `subTotal` instead. 18 | 19 | ### Coupons 20 | 21 | * `code` removed 22 | * `forItem` removed 23 | * `discount($throwErrors = false)` -> `discount()` 24 | * `getFailedMessage` removed 25 | 26 | ### LaraCart 27 | 28 | * `total($format = true, $withDiscount = true, $withTax = true, $withFees = true)` -> `total($format = true);` 29 | * `subTotal($format = true, $withDiscount = true)` -> `subTotal($format = true);` 30 | * `feeTotals($format = true);` -> `feeSubTotal($format = true);` 31 | * `taxTotal($format = true, $withFees = true, $grossTaxes = true, $withDiscounts = true)` -> `taxTotal($format = true)` 32 | * `totalDiscount($format = true, $withItemDiscounts = true)` -> `discountTotal($format = true)` 33 | 34 | ### Config 35 | 36 | * `tax_by_item` removed (now the default) 37 | * `tax_item_before_discount` removed 38 | * `round_every_item_price` removed (now the default) 39 | * `discountTaxable` removed 40 | * `discountsAlreadyTaxed` removed 41 | * `discountOnFees` -> `discount_fees` 42 | * `fees_taxable` added --------------------------------------------------------------------------------