├── .gitignore
├── .release.json
├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── src
├── Exceptions
│ ├── InvalidPrice.php
│ ├── ModelNotFound.php
│ ├── CouponException.php
│ ├── InvalidQuantity.php
│ └── InvalidTaxableValue.php
├── LaraCartHasher.php
├── Facades
│ └── LaraCart.php
├── Contracts
│ ├── CouponContract.php
│ └── LaraCartContract.php
├── Cart.php
├── database
│ └── migrations
│ │ └── add_cart_session_id_to_users_table.php.stub
├── Coupons
│ ├── Percentage.php
│ └── Fixed.php
├── CartFee.php
├── LaraCartServiceProvider.php
├── CartSubItem.php
├── Traits
│ ├── CartOptionsMagicMethodsTrait.php
│ └── CouponTrait.php
├── config
│ └── laracart.php
├── CartItem.php
└── LaraCart.php
├── tests
├── Models
│ ├── User.php
│ └── TestItem.php
├── MagicFunctionsTest.php
├── CrossDeviceTest.php
├── Coupons
│ └── Fixed.php
├── LaraCartTestTrait.php
├── FeesTest.php
├── LaraCartTest.php
├── ItemRelationTest.php
├── SubItemsTest.php
├── ItemsTest.php
├── TotalsTest.php
└── CouponsTest.php
├── readme.md
├── phpunit.xml
├── LICENSE
└── composer.json
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.lock
3 | build
4 | .phpunit.result.cache
--------------------------------------------------------------------------------
/.release.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "release": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [lukepolo]
4 |
--------------------------------------------------------------------------------
/src/Exceptions/InvalidPrice.php:
--------------------------------------------------------------------------------
1 | cart_sessoin_id = $this->getAttribute('cart_sessoin_id');
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Contracts/CouponContract.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 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ## LaraCart - Laravel Shopping Cart Package
2 |
3 | [](https://packagist.org/packages/lukepolo/laracart)
4 | [](https://packagist.org/packages/lukepolo/laracart)
5 |
6 | ### Documentation
7 |
8 | http://laracart.lukepolo.com
9 |
10 | ## Features
11 |
12 | - Coupons
13 | - Session Based System
14 | - Cross Device Support
15 | - Multiple cart instances
16 | - Fees such as a delivery fee
17 | - Taxation on a the item level
18 | - Prices display currency and locale
19 | - Endless item chaining for complex systems
20 | - Totals of all items within the item chains
21 | - Item Model Relation at a global and item level
22 | - Quickly insert items with your own item models
23 |
24 | ## Installation
25 |
26 | ```bash
27 | composer require lukepolo/laracart
28 | ```
29 |
30 | Publish vendor config and migration:
31 |
32 | ```bash
33 | php artisan vendor:publish --provider="LukePOLO\LaraCart\LaraCartServiceProvider
34 | ```
35 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ./src
16 |
17 |
18 | ./vendor
19 | ./tests
20 |
21 |
22 |
23 |
24 | tests
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/tests/Models/TestItem.php:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------
/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": "^8.2",
21 | "ext-intl": "*",
22 | "illuminate/support": "^11.0 || ^12.0",
23 | "illuminate/session": "^11.0 || ^12.0",
24 | "illuminate/events": "^11.0 || ^12.0"
25 | },
26 | "require-dev": {
27 | "phpunit/phpunit": "^10.5 || ^11.5.3",
28 | "orchestra/testbench": "^10.0",
29 | "mockery/mockery": "^1.0",
30 | "phpunit/php-code-coverage": "^11.0"
31 | },
32 | "autoload": {
33 | "psr-4": {
34 | "LukePOLO\\LaraCart\\": "src/"
35 | }
36 | },
37 | "autoload-dev": {
38 | "psr-4": {
39 | "LukePOLO\\LaraCart\\Tests\\": "tests"
40 | }
41 | },
42 | "extra": {
43 | "laravel": {
44 | "providers": [
45 | "LukePOLO\\LaraCart\\LaraCartServiceProvider"
46 | ],
47 | "aliases": {
48 | "LaraCart": "LukePOLO\\LaraCart\\Facades\\LaraCart"
49 | }
50 | }
51 | },
52 | "minimum-stability": "stable"
53 | }
54 |
--------------------------------------------------------------------------------
/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/Coupons/Fixed.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/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 - $this->discounted;
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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/src/LaraCartServiceProvider.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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/Contracts/LaraCartContract.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/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 | // Flatten the taxSummary output
320 | $taxSummary = $item->taxSummary();
321 | $flat = [];
322 | foreach ($taxSummary as $qtySummary) {
323 | foreach ($qtySummary as $taxRate => $amount) {
324 | if (!isset($flat[$taxRate])) {
325 | $flat[$taxRate] = 0;
326 | }
327 | $flat[$taxRate] += $amount;
328 | }
329 | }
330 |
331 | $this->assertEquals([
332 | '0.01' => .10,
333 | '0.02' => .20,
334 | ], $flat);
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/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 | // Exclude private and protected properties. https://www.php.net/manual/en/language.types.array.php#language.types.array.casting
95 | foreach ($cartItemArray as $key => $value) {
96 | if ($key[0] === "\0") {
97 | unset($cartItemArray[$key]);
98 | }
99 | }
100 |
101 | unset($cartItemArray['discounted']);
102 | unset($cartItemArray['options']['qty']);
103 | unset($cartItemArray['options']['model']);
104 |
105 | foreach ($this->excludeFromHash as $option) {
106 | unset($cartItemArray['options'][$option]);
107 | }
108 |
109 | ksort($cartItemArray['options']);
110 |
111 | $this->itemHash = app(LaraCart::HASH)->hash($cartItemArray);
112 | } elseif ($force || empty($this->itemHash) === true) {
113 | $this->itemHash = app(LaraCart::RANHASH);
114 | }
115 |
116 | app('events')->dispatch(
117 | 'laracart.updateItem',
118 | [
119 | 'item' => $this,
120 | 'newHash' => $this->itemHash,
121 | ]
122 | );
123 |
124 | return $this->itemHash;
125 | }
126 |
127 | /**
128 | * Gets the hash for the item.
129 | *
130 | * @return mixed
131 | */
132 | public function getHash()
133 | {
134 | return $this->itemHash;
135 | }
136 |
137 | /**
138 | * Search for matching options on the item.
139 | *
140 | * @return mixed
141 | */
142 | public function find($data)
143 | {
144 | foreach ($data as $key => $value) {
145 | if ($this->$key !== $value) {
146 | return false;
147 | }
148 | }
149 |
150 | return $this;
151 | }
152 |
153 | /**
154 | * Finds a sub item by its hash.
155 | *
156 | * @param $subItemHash
157 | *
158 | * @return mixed
159 | */
160 | public function findSubItem($subItemHash)
161 | {
162 | return Arr::get($this->subItems, $subItemHash);
163 | }
164 |
165 | /**
166 | * Adds an sub item to a item.
167 | *
168 | * @param array $subItem
169 | *
170 | * @return CartSubItem
171 | */
172 | public function addSubItem(array $subItem)
173 | {
174 | $subItem = new CartSubItem($subItem);
175 |
176 | $this->subItems[$subItem->getHash()] = $subItem;
177 |
178 | $this->update();
179 |
180 | return $subItem;
181 | }
182 |
183 | /**
184 | * Removes a sub item from the item.
185 | *
186 | * @param $subItemHash
187 | */
188 | public function removeSubItem($subItemHash)
189 | {
190 | unset($this->subItems[$subItemHash]);
191 |
192 | $this->update();
193 | }
194 |
195 | public function getPrice()
196 | {
197 | return $this->price;
198 | }
199 |
200 | /**
201 | * Gets the price of the item with or without tax, with the proper format.
202 | *
203 | * @return string
204 | */
205 | public function total()
206 | {
207 | $total = 0;
208 |
209 | if ($this->active) {
210 | for ($qty = 0; $qty < $this->qty; $qty++) {
211 | $total += LaraCart::formatMoney($this->subTotalPerItem(false) + array_sum($this->taxSummary()[$qty]), null, null, false);
212 | }
213 |
214 | $total -= $this->getDiscount(false);
215 |
216 | if ($total < 0) {
217 | $total = 0;
218 | }
219 | }
220 |
221 | return $total;
222 | }
223 |
224 | public function taxTotal()
225 | {
226 | $total = 0;
227 |
228 | foreach ($this->taxSummary() as $itemSummary) {
229 | $total += array_sum($itemSummary);
230 | }
231 |
232 | return $total;
233 | }
234 |
235 | /**
236 | * Gets the sub total of the item based on the qty.
237 | *
238 | * @param bool $format
239 | *
240 | * @return float|string
241 | */
242 | public function subTotal()
243 | {
244 | return $this->subTotalPerItem() * $this->qty;
245 | }
246 |
247 | public function subTotalPerItem()
248 | {
249 | $subTotal = $this->active ? ($this->price + $this->subItemsTotal()) : 0;
250 |
251 | return $subTotal;
252 | }
253 |
254 | /**
255 | * Gets the totals for the options.
256 | *
257 | * @return float
258 | */
259 | public function subItemsTotal()
260 | {
261 | $total = 0;
262 |
263 | foreach ($this->subItems as $subItem) {
264 | $total += $subItem->subTotal(false);
265 | }
266 |
267 | return $total;
268 | }
269 |
270 | /**
271 | * Gets the discount of an item.
272 | *
273 | * @return string
274 | */
275 | public function getDiscount()
276 | {
277 | return array_sum($this->discounted);
278 | }
279 |
280 | /**
281 | * @param CouponContract $coupon
282 | *
283 | * @return $this
284 | */
285 | public function addCoupon(CouponContract $coupon)
286 | {
287 | $coupon->appliedToCart = false;
288 | app('laracart')->addCoupon($coupon);
289 | $this->coupon = $coupon;
290 |
291 | return $this;
292 | }
293 |
294 | public function taxSummary()
295 | {
296 | $taxed = [];
297 | // tax item by item
298 | for ($qty = 0; $qty < $this->qty; $qty++) {
299 | // keep track of what is discountable
300 | $discountable = $this->discounted[$qty] ?? 0;
301 | $price = ($this->taxable ? $this->price : 0);
302 |
303 | $taxable = $price - ($discountable > 0 ? $discountable : 0);
304 | // track what has been discounted so far
305 | $discountable = $discountable - $price;
306 |
307 | $taxed[$qty] = [];
308 | if ($taxable > 0) {
309 | if (!isset($taxed[$qty][(string) $this->tax])) {
310 | $taxed[$qty][(string) $this->tax] = 0;
311 | }
312 | $taxed[$qty][(string) $this->tax] += $taxable * $this->tax;
313 | }
314 |
315 | // tax sub item item by sub item
316 | foreach ($this->subItems as $subItem) {
317 | $subItemTaxable = 0;
318 | for ($subItemQty = 0; $subItemQty < ($subItem->qty || 1); $subItemQty++) {
319 | $subItemPrice = ($subItem->taxable ?? true) ? $subItem->price : 0;
320 | $subItemTaxable = $subItemPrice - ($discountable > 0 ? $discountable : 0);
321 | $discountable = $discountable - $subItemPrice;
322 | }
323 |
324 | if ($subItemTaxable > 0) {
325 | if (!isset($taxed[$qty][(string) $subItem->tax])) {
326 | $taxed[$qty][(string) $subItem->tax] = 0;
327 | }
328 | $taxed[$qty][(string) $subItem->tax] += $subItemTaxable * $subItem->tax;
329 | }
330 |
331 | // discount sub items ... items
332 | if (isset($subItem->items)) {
333 | foreach ($subItem->items as $item) {
334 | if ($item->taxable) {
335 | foreach ($item->taxSummary() as $itemTaxSummary) {
336 | foreach ($itemTaxSummary as $taxRate => $amount) {
337 | if (!isset($taxed[$qty][(string) $taxRate])) {
338 | $taxed[$qty][(string) $taxRate] = 0;
339 | }
340 | $taxed[$qty][(string) $taxRate] += $amount;
341 | }
342 | }
343 | }
344 | }
345 | }
346 | }
347 | }
348 |
349 | return $taxed;
350 | }
351 |
352 | /**
353 | * Sets the related model to the item.
354 | *
355 | * @param $itemModel
356 | * @param array $relations
357 | *
358 | * @throws ModelNotFound
359 | */
360 | public function setModel($itemModel, $relations = [])
361 | {
362 | if (!class_exists($itemModel)) {
363 | throw new ModelNotFound('Could not find relation model');
364 | }
365 |
366 | $this->itemModel = $itemModel;
367 | $this->itemModelRelations = $relations;
368 | }
369 |
370 | /**
371 | * Gets the items model class.
372 | */
373 | public function getItemModel()
374 | {
375 | return $this->itemModel;
376 | }
377 |
378 | /**
379 | * Returns a Model.
380 | *
381 | * @throws ModelNotFound
382 | */
383 | public function getModel()
384 | {
385 | $itemModel = (new $this->itemModel())->with($this->itemModelRelations)->find($this->id);
386 |
387 | if (empty($itemModel)) {
388 | throw new ModelNotFound('Could not find the item model for '.$this->id);
389 | }
390 |
391 | return $itemModel;
392 | }
393 |
394 | /**
395 | * A way to find sub items.
396 | *
397 | * @param $data
398 | *
399 | * @return array
400 | */
401 | public function searchForSubItem($data)
402 | {
403 | $matches = [];
404 |
405 | foreach ($this->subItems as $subItem) {
406 | if ($subItem->find($data)) {
407 | $matches[] = $subItem;
408 | }
409 | }
410 |
411 | return $matches;
412 | }
413 |
414 | public function disable()
415 | {
416 | $this->active = false;
417 | $this->update();
418 | }
419 |
420 | public function enable()
421 | {
422 | $this->active = true;
423 | $this->update();
424 | }
425 |
426 | public function update()
427 | {
428 | $this->generateHash();
429 | app('laracart')->update();
430 | }
431 | }
432 |
--------------------------------------------------------------------------------
/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->assertEqualsWithDelta(32.1, $item->total(false), 0.0001);
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/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.02, $this->laracart->discountTotal(false));
343 | $this->assertEquals(1085.25, $this->laracart->taxTotal(false));
344 | $this->assertEquals(6253.10, $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 |
--------------------------------------------------------------------------------
/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->assertEqualsWithDelta(90 * 1.07, $this->laracart->total(false), 0.0001);
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 |
--------------------------------------------------------------------------------
/src/LaraCart.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 | $remainingDiscount = $coupon->value - $coupon->discounted;
175 | if ($remainingDiscount > 0) {
176 | $fee->discounted = min($remainingDiscount, $fee->amount);
177 | $coupon->discounted += $fee->discounted;
178 | }
179 | }
180 | }
181 | }
182 | }
183 |
184 | /**
185 | * Updates cart session.
186 | */
187 | public function update()
188 | {
189 | // allows us to track a discount on the item so we are able properly do taxation
190 | $this->updateDiscounts();
191 |
192 | $this->session->put($this->prefix.'.'.$this->cart->instance, $this->cart);
193 |
194 | if (config('laracart.cross_devices', false) && $this->authManager->check()) {
195 | $this->authManager->user()->cart_session_id = $this->session->getId();
196 | $this->authManager->user()->save();
197 | }
198 |
199 | $this->session->reflash();
200 |
201 | $this->session->save();
202 |
203 | $this->events->dispatch('laracart.update', $this->cart);
204 | }
205 |
206 | /**
207 | * Removes an attribute from the cart.
208 | *
209 | * @param $attribute
210 | */
211 | public function removeAttribute($attribute)
212 | {
213 | Arr::forget($this->cart->attributes, $attribute);
214 |
215 | $this->update();
216 | }
217 |
218 | /**
219 | * Creates a CartItem and then adds it to cart.
220 | *
221 | * @param string|int $itemID
222 | * @param null $name
223 | * @param int $qty
224 | * @param string $price
225 | * @param array $options
226 | * @param bool|true $taxable
227 | *
228 | * @throws ModelNotFound
229 | *
230 | * @return CartItem
231 | */
232 | public function addLine($itemID, $name = null, $qty = 1, $price = '0.00', $options = [], $taxable = true)
233 | {
234 | return $this->add($itemID, $name, $qty, $price, $options, $taxable, true);
235 | }
236 |
237 | /**
238 | * Creates a CartItem and then adds it to cart.
239 | *
240 | * @param $itemID
241 | * @param null $name
242 | * @param int $qty
243 | * @param string $price
244 | * @param array $options
245 | * @param bool|false $taxable
246 | * @param bool|false $lineItem
247 | *
248 | * @throws ModelNotFound
249 | *
250 | * @return CartItem
251 | */
252 | public function add(
253 | $itemID,
254 | $name = null,
255 | $qty = 1,
256 | $price = '0.00',
257 | $options = [],
258 | $taxable = true,
259 | $lineItem = false
260 | ) {
261 | if (!empty(config('laracart.item_model'))) {
262 | $itemModel = $itemID;
263 |
264 | if (!$this->isItemModel($itemModel)) {
265 | $itemModel = (new $this->itemModel())->with($this->itemModelRelations)->find($itemID);
266 | }
267 |
268 | if (empty($itemModel)) {
269 | throw new ModelNotFound('Could not find the item '.$itemID);
270 | }
271 |
272 | $bindings = config('laracart.item_model_bindings');
273 |
274 | $itemID = $itemModel->{$bindings[\LukePOLO\LaraCart\CartItem::ITEM_ID]};
275 |
276 | if (is_int($name)) {
277 | $qty = $name;
278 | }
279 |
280 | $name = $itemModel->{$bindings[\LukePOLO\LaraCart\CartItem::ITEM_NAME]};
281 | $price = $itemModel->{$bindings[\LukePOLO\LaraCart\CartItem::ITEM_PRICE]};
282 |
283 | $options['model'] = $itemModel;
284 |
285 | $options = array_merge($options, $this->getItemModelOptions($itemModel, $bindings[\LukePOLO\LaraCart\CartItem::ITEM_OPTIONS]));
286 |
287 | $taxable = $itemModel->{$bindings[\LukePOLO\LaraCart\CartItem::ITEM_TAXABLE]} ? true : false;
288 | }
289 |
290 | $item = $this->addItem(new CartItem(
291 | $itemID,
292 | $name,
293 | $qty,
294 | $price,
295 | $options,
296 | $taxable,
297 | $lineItem
298 | ));
299 |
300 | $this->update();
301 |
302 | return $this->getItem($item->getHash());
303 | }
304 |
305 | /**
306 | * Adds the cartItem into the cart session.
307 | *
308 | * @param CartItem $cartItem
309 | *
310 | * @return CartItem
311 | */
312 | public function addItem(CartItem $cartItem)
313 | {
314 | $itemHash = $cartItem->generateHash();
315 |
316 | if ($this->getItem($itemHash)) {
317 | $this->getItem($itemHash)->qty += $cartItem->qty;
318 | } else {
319 | $this->cart->items[] = $cartItem;
320 | }
321 |
322 | app('events')->dispatch(
323 | 'laracart.addItem',
324 | $cartItem
325 | );
326 |
327 | return $cartItem;
328 | }
329 |
330 | /**
331 | * Increment the quantity of a cartItem based on the itemHash.
332 | *
333 | * @param $itemHash
334 | *
335 | * @return CartItem | null
336 | */
337 | public function increment($itemHash)
338 | {
339 | $item = $this->getItem($itemHash);
340 | $item->qty++;
341 | $this->update();
342 |
343 | return $item;
344 | }
345 |
346 | /**
347 | * Decrement the quantity of a cartItem based on the itemHash.
348 | *
349 | * @param $itemHash
350 | *
351 | * @return CartItem | null
352 | */
353 | public function decrement($itemHash)
354 | {
355 | $item = $this->getItem($itemHash);
356 | if ($item->qty > 1) {
357 | $item->qty--;
358 | $this->update();
359 |
360 | return $item;
361 | }
362 | $this->removeItem($itemHash);
363 | $this->update();
364 | }
365 |
366 | /**
367 | * Find items in the cart matching a data set.
368 | *
369 | * param $data
370 | *
371 | * @return array | CartItem | null
372 | */
373 | public function find($data)
374 | {
375 | $matches = [];
376 |
377 | foreach ($this->getItems() as $item) {
378 | if ($item->find($data)) {
379 | $matches[] = $item;
380 | }
381 | }
382 |
383 | switch (count($matches)) {
384 | case 0:
385 | return;
386 | break;
387 | case 1:
388 | return $matches[0];
389 | break;
390 | default:
391 | return $matches;
392 | }
393 | }
394 |
395 | /**
396 | * Finds a cartItem based on the itemHash.
397 | *
398 | * @param $itemHash
399 | *
400 | * @return CartItem | null
401 | */
402 | public function getItem($itemHash)
403 | {
404 | return Arr::get($this->getItems(), $itemHash);
405 | }
406 |
407 | /**
408 | * Gets all the items within the cart.
409 | *
410 | * @return array
411 | */
412 | public function getItems()
413 | {
414 | $items = [];
415 | if (isset($this->cart->items) === true) {
416 | foreach ($this->cart->items as $item) {
417 | $items[$item->getHash()] = $item;
418 | }
419 | }
420 |
421 | return $items;
422 | }
423 |
424 | /**
425 | * Updates an items attributes.
426 | *
427 | * @param $itemHash
428 | * @param $key
429 | * @param $value
430 | *
431 | * @return CartItem|null
432 | */
433 | public function updateItem($itemHash, $key, $value)
434 | {
435 | if (empty($item = $this->getItem($itemHash)) === false) {
436 | if ($key == 'qty' && $value == 0) {
437 | return $this->removeItem($itemHash);
438 | }
439 |
440 | $item->$key = $value;
441 | }
442 |
443 | $this->update();
444 |
445 | return $item;
446 | }
447 |
448 | /**
449 | * Removes a CartItem based on the itemHash.
450 | *
451 | * @param $itemHash
452 | */
453 | public function removeItem($itemHash)
454 | {
455 | if (empty($this->cart->items) === false) {
456 | foreach ($this->cart->items as $itemKey => $item) {
457 | if ($item->getHash() == $itemHash) {
458 | unset($this->cart->items[$itemKey]);
459 | break;
460 | }
461 | }
462 |
463 | $this->events->dispatch('laracart.removeItem', $item);
464 |
465 | $this->update();
466 | }
467 | }
468 |
469 | /**
470 | * Empties the carts items.
471 | */
472 | public function emptyCart()
473 | {
474 | unset($this->cart->items);
475 |
476 | $this->update();
477 |
478 | $this->events->dispatch('laracart.empty', $this->cart->instance);
479 | }
480 |
481 | /**
482 | * Completely destroys cart and anything associated with it.
483 | */
484 | public function destroyCart()
485 | {
486 | $instance = $this->cart->instance;
487 |
488 | $this->session->forget($this->prefix.'.'.$instance);
489 |
490 | $this->events->dispatch('laracart.destroy', $instance);
491 |
492 | $this->cart = new Cart($instance);
493 |
494 | $this->update();
495 | }
496 |
497 | /**
498 | * Gets the coupons for the current cart.
499 | *
500 | * @return array
501 | */
502 | public function getCoupons()
503 | {
504 | return $this->cart->coupons;
505 | }
506 |
507 | /**
508 | * Finds a specific coupon in the cart.
509 | *
510 | * @param $code
511 | *
512 | * @return mixed
513 | */
514 | public function findCoupon($code)
515 | {
516 | return Arr::get($this->cart->coupons, $code);
517 | }
518 |
519 | /**
520 | * Applies a coupon to the cart.
521 | *
522 | * @param CouponContract $coupon
523 | */
524 | public function addCoupon(CouponContract $coupon)
525 | {
526 | if (!$this->cart->multipleCoupons) {
527 | $this->cart->coupons = [];
528 | }
529 |
530 | $this->cart->coupons[$coupon->code] = $coupon;
531 |
532 | $this->update();
533 | }
534 |
535 | /**
536 | * Removes a coupon in the cart.
537 | *
538 | * @param $code
539 | */
540 | public function removeCoupon($code)
541 | {
542 | $this->removeCouponFromItems($code);
543 | Arr::forget($this->cart->coupons, $code);
544 | $this->update();
545 | }
546 |
547 | /**
548 | * Removes all coupons from the cart.
549 | */
550 | public function removeCoupons()
551 | {
552 | $this->removeCouponFromItems();
553 | $this->cart->coupons = [];
554 | $this->update();
555 | }
556 |
557 | /**
558 | * Gets a specific fee from the fees array.
559 | *
560 | * @param $name
561 | *
562 | * @return mixed
563 | */
564 | public function getFee($name)
565 | {
566 | return Arr::get($this->cart->fees, $name, new CartFee(null, false));
567 | }
568 |
569 | /**
570 | * Allows to charge for additional fees that may or may not be taxable
571 | * ex - service fee , delivery fee, tips.
572 | *
573 | * @param $name
574 | * @param $amount
575 | * @param bool|false $taxable
576 | * @param array $options
577 | */
578 | public function addFee($name, $amount, $taxable = false, array $options = [])
579 | {
580 | Arr::set($this->cart->fees, $name, new CartFee($amount, $taxable, $options));
581 |
582 | $this->update();
583 | }
584 |
585 | /**
586 | * Removes a fee from the fee array.
587 | *
588 | * @param $name
589 | */
590 | public function removeFee($name)
591 | {
592 | Arr::forget($this->cart->fees, $name);
593 |
594 | $this->update();
595 | }
596 |
597 | /**
598 | * Removes all the fees set in the cart.
599 | */
600 | public function removeFees()
601 | {
602 | $this->cart->fees = [];
603 |
604 | $this->update();
605 | }
606 |
607 | /**
608 | * Gets the total tax for the cart.
609 | *
610 | * @param bool|true $format
611 | *
612 | * @return string
613 | */
614 | public function taxTotal($format = true)
615 | {
616 | $totalTax = 0;
617 |
618 | foreach ($this->getItems() as $item) {
619 | $totalTax += $item->taxTotal(false);
620 | }
621 |
622 | $totalTax += $this->feeTaxTotal(false);
623 |
624 | return $this->formatMoney($totalTax, null, null, $format);
625 | }
626 |
627 | public function feeTaxTotal($format = true)
628 | {
629 | return $this->formatMoney(array_sum($this->feeTaxSummary()), null, null, $format);
630 | }
631 |
632 | public function feeTaxSummary()
633 | {
634 | $taxed = [];
635 | if (config('laracart.fees_taxable', false)) {
636 | foreach ($this->getFees() as $fee) {
637 | if ($fee->taxable) {
638 | if (!isset($taxed[(string) $fee->tax])) {
639 | $taxed[(string) $fee->tax] = 0;
640 | }
641 | $taxed[(string) $fee->tax] += $this->formatMoney($fee->amount * $fee->tax, null, null, false);
642 | }
643 | }
644 | }
645 |
646 | return $taxed;
647 | }
648 |
649 | public function taxSummary()
650 | {
651 | $taxed = [];
652 | foreach ($this->getItems() as $item) {
653 | foreach ($item->taxSummary() as $qtyIndex => $taxRates) {
654 | foreach ($taxRates as $taxRate => $amount) {
655 | if (!isset($taxed[(string) $taxRate])) {
656 | $taxed[(string) $taxRate] = 0;
657 | }
658 | $taxed[(string) $taxRate] += $amount;
659 | }
660 | }
661 | }
662 |
663 | foreach ($this->feeTaxSummary() as $taxRate => $amount) {
664 | if (!isset($taxed[(string) $taxRate])) {
665 | $taxed[(string) $taxRate] = 0;
666 | }
667 | $taxed[(string) $taxRate] += $amount;
668 | }
669 |
670 | return $taxed;
671 | }
672 |
673 | /**
674 | * Gets the total of the cart with or without tax.
675 | *
676 | * @param bool $format
677 | *
678 | * @return string
679 | */
680 | public function total($format = true)
681 | {
682 | $total = $this->itemTotals(false);
683 | $total += $this->feeSubTotal(false) + $this->feeTaxTotal(false);
684 |
685 | return $this->formatMoney($total, null, null, $format);
686 | }
687 |
688 | public function netTotal($format = true)
689 | {
690 | $total = $this->subTotal(false);
691 | $total += $this->feeSubTotal(false);
692 | $total -= $this->discountTotal(false);
693 |
694 | return $this->formatMoney($total, null, null, $format);
695 | }
696 |
697 | public function itemTotals($format = true)
698 | {
699 | $total = 0;
700 |
701 | if ($this->count() != 0) {
702 | foreach ($this->getItems() as $item) {
703 | $total += $item->total(false);
704 | }
705 | }
706 |
707 | if ($total < 0) {
708 | $total = 0;
709 | }
710 |
711 | return $this->formatMoney($total, null, null, $format);
712 | }
713 |
714 | /**
715 | * Gets the subtotal of the cart with or without tax.
716 | *
717 | * @param bool $format
718 | *
719 | * @return string
720 | */
721 | public function subTotal($format = true)
722 | {
723 | $total = 0;
724 |
725 | if ($this->count() != 0) {
726 | foreach ($this->getItems() as $item) {
727 | $total += $item->subTotal(false);
728 | }
729 | }
730 |
731 | if ($total < 0) {
732 | $total = 0;
733 | }
734 |
735 | return $this->formatMoney($total, null, null, $format);
736 | }
737 |
738 | /**
739 | * Get the count based on qty, or number of unique items.
740 | *
741 | * @param bool $withItemQty
742 | *
743 | * @return int
744 | */
745 | public function count($withItemQty = true)
746 | {
747 | $count = 0;
748 |
749 | foreach ($this->getItems() as $item) {
750 | if ($withItemQty) {
751 | $count += $item->qty;
752 | } else {
753 | $count++;
754 | }
755 | }
756 |
757 | return $count;
758 | }
759 |
760 | /**
761 | * Formats the number into a money format based on the locale and currency formats.
762 | *
763 | * @param $number
764 | * @param $locale
765 | * @param $currencyCode
766 | * @param $format
767 | *
768 | * @return string
769 | */
770 | public static function formatMoney($number, $locale = null, $currencyCode = null, $format = true)
771 | {
772 | // When prices in cents needs to be formatted, divide by 100 to allow formatting in whole units
773 | if (config('laracart.prices_in_cents', false) === true && $format) {
774 | $number = $number / 100;
775 | // When prices in cents do not need to be formatted then cast to integer and round the price
776 | } elseif (config('laracart.prices_in_cents', false) === true && !$format) {
777 | $number = (int) round($number);
778 | } else {
779 | $number = round($number, 2);
780 | }
781 |
782 | if ($format) {
783 | $moneyFormatter = new NumberFormatter(empty($locale) ? config('laracart.locale', 'en_US.UTF-8') : $locale, NumberFormatter::CURRENCY);
784 |
785 | $number = $moneyFormatter->formatCurrency($number, empty($currencyCode) ? config('laracart.currency_code', 'USD') : $currencyCode);
786 | }
787 |
788 | return $number;
789 | }
790 |
791 | public function feeSubTotal($format = true)
792 | {
793 | $feeTotal = 0;
794 |
795 | foreach ($this->getFees() as $fee) {
796 | $feeTotal += $fee->getAmount(false);
797 | }
798 |
799 | return $this->formatMoney($feeTotal, null, null, $format);
800 | }
801 |
802 | /**
803 | * Gets all the fees on the cart object.
804 | *
805 | * @return mixed
806 | */
807 | public function getFees()
808 | {
809 | return $this->cart->fees;
810 | }
811 |
812 | /**
813 | * Gets the total amount discounted.
814 | *
815 | * @param bool $format
816 | *
817 | * @return string
818 | */
819 | public function discountTotal($format = true)
820 | {
821 | $total = 0;
822 |
823 | foreach ($this->getItems() as $item) {
824 | $total += $item->getDiscount(false);
825 | }
826 |
827 | foreach ($this->getFees() as $fee) {
828 | $total += $fee->getDiscount(false);
829 | }
830 |
831 | return $this->formatMoney($total, null, null, $format);
832 | }
833 |
834 | /**
835 | * Checks to see if its an item model.
836 | *
837 | * @param $itemModel
838 | *
839 | * @return bool
840 | */
841 | private function isItemModel($itemModel)
842 | {
843 | if (is_object($itemModel) && get_class($itemModel) == config('laracart.item_model')) {
844 | return true;
845 | }
846 |
847 | return false;
848 | }
849 |
850 | /**
851 | * Gets the item models options based the config.
852 | *
853 | * @param Model $itemModel
854 | * @param array $options
855 | *
856 | * @return array
857 | */
858 | private function getItemModelOptions(Model $itemModel, $options = [])
859 | {
860 | $itemOptions = [];
861 | foreach ($options as $option) {
862 | $itemOptions[$option] = $this->getFromModel($itemModel, $option);
863 | }
864 |
865 | return array_filter($itemOptions, function ($value) {
866 | if ($value !== false && empty($value)) {
867 | return false;
868 | }
869 |
870 | return true;
871 | });
872 | }
873 |
874 | /**
875 | * Gets a option from the model.
876 | *
877 | * @param Model $itemModel
878 | * @param $attr
879 | * @param null $defaultValue
880 | *
881 | * @return Model|null
882 | */
883 | private function getFromModel(Model $itemModel, $attr, $defaultValue = null)
884 | {
885 | $variable = $itemModel;
886 |
887 | if (!empty($attr)) {
888 | foreach (explode('.', $attr) as $attr) {
889 | $variable = Arr::get($variable, $attr, $defaultValue);
890 | }
891 | }
892 |
893 | return $variable;
894 | }
895 |
896 | /**
897 | * Removes a coupon from the item.
898 | *
899 | * @param null $code
900 | */
901 | private function removeCouponFromItems($code = null)
902 | {
903 | foreach ($this->getItems() as $item) {
904 | if (isset($item->coupon) && (empty($code) || $item->coupon->code == $code)) {
905 | $item->coupon = null;
906 | }
907 | }
908 | }
909 | }
910 |
--------------------------------------------------------------------------------