Compare commits

...
34 Commits
Author SHA1 Message Date
Joel Male e810eb4ef4 Merge pull request #2 from joelwmale/feature/database-support
Database Support
2025-02-25 16:55:44 +10:00
Joel Male 4cf078277d feat: finish tests & docs 2025-02-25 16:54:03 +10:00
Joel Male 7adf5ecd1f feat: add tests and fix item level conditions for database storage 2025-02-25 16:31:42 +10:00
Joel Male 158cf38f14 feat: update composer deps & version 2025-02-25 16:08:27 +10:00
Joel Male 0f07b9ae8a feat: database support 2025-02-25 16:03:17 +10:00
Joel Male ec99aa0563 feat: clean up and starting documentation 2025-02-25 14:53:49 +10:00
Joel Male 8d6e1bcc26 wip: readme updates 2025-02-25 13:49:09 +10:00
Joel Male b92dce9ca4 wip: initial working database support 2025-02-25 11:47:10 +10:00
Joel Male 2196f58f01 Merge pull request #1 from joelwmale/feature/laravel-12.x-support
Laravel 12.x Support
2025-02-25 08:57:01 +10:00
Joel Male ed4d112665 Laravel 12.x Support 2025-02-25 08:54:04 +10:00
Joel Male f60bf92157 1.0.12 2024-11-28 13:11:23 +10:00
Joel Male a231ed8c43 1.0.12 2024-11-28 13:06:30 +10:00
Joel Male 599051631c wip: rename package to joelwmale 2024-11-28 13:06:24 +10:00
Joel Male b3ef4c38fc 1.0.11 2024-09-23 07:48:27 +10:00
Joel Male a45121ca63 Merge pull request #20 from wearepixel/task/fix-subtotal-always-being-used-if-the-total-is-$0
Task: Fix subtotal always being used when cart conditions on the total bring it to $0
2024-09-23 07:48:06 +10:00
Joel Male 5505f121c7 fix subtotal always being used 2024-09-23 07:45:59 +10:00
Joel Male 472d505041 wip: fix issue with conditions not being properly removed 2024-09-18 15:56:12 +10:00
Joel Male c6c4240c5f 1.0.9 2024-09-08 18:27:44 +10:00
Joel Male 54d7c8a54f Merge pull request #17 from wearepixel/feature/livewire-support-for-nested-collections
Better Livewire Support
2024-09-08 12:23:22 +10:00
Joel Male a8d743eb9b remove php formatting 2024-09-08 12:22:03 +10:00
Pixel Tech b7381573e8 Apply Laravel Pint
Signed-off-by: Pixel Tech <tech@wearepixel.com.au>
2024-09-08 02:21:01 +00:00
Joel Male d4d4df7d58 pint fixes 2024-09-08 12:20:36 +10:00
Pixel Tech 6ea2cb3b8b Apply Laravel Pint
Signed-off-by: Pixel Tech <tech@wearepixel.com.au>
2024-09-07 22:36:00 +00:00
Joel Male 7a2c41e02d add better livewire support
- and reset collection indexes properly
2024-09-08 08:32:51 +10:00
Joel Male 9a59857825 Merge pull request #12 from wearepixel/feature/targets-to-activate-cart-conditions
Feature: Add targets to cart conditions
2024-09-04 09:38:34 +10:00
Joel Male 43238b6f2e revert php 8.0 support 2024-09-04 09:33:49 +10:00
Joel Male c0f0fcd2f0 fix 8.0 tests 2024-09-04 09:30:28 +10:00
Joel Male 185c4572ae fix tests 2024-09-04 09:26:50 +10:00
Pixel Tech c111cda3aa Apply Laravel Pint
Signed-off-by: Pixel Tech <tech@wearepixel.com.au>
2024-09-03 23:16:53 +00:00
Joel Male ab9eb6c873 fix tests 2024-09-04 09:16:15 +10:00
Joel Male 7ee26add8d undo extra changes 2024-09-04 09:14:23 +10:00
Joel Male 05318b7b13 working on deprecations and code cleanup 2024-09-03 21:41:45 +10:00
Joel Male 4d2efcdeae fix tests for cart conditions 2024-09-03 21:07:58 +10:00
Joel Male a269c188e4 initial functionality 2024-09-03 20:22:03 +10:00
41 changed files with 4984 additions and 1975 deletions
-25
View File
@@ -1,25 +0,0 @@
name: PHP Formatting
on: pull_request
jobs:
php-formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: "Run Pint"
uses: aglipanci/laravel-pint-action@latest
with:
verboseMode: true
configPath: "pint.json"
pintVersion: 1.17.2
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Apply Laravel Pint"
commit_options: '--no-verify --signoff'
file_pattern: "*.php"
commit_user_name: "Pixel Tech"
commit_user_email: "tech@wearepixel.com.au"
commit_author: "Pixel Tech<tech@wearepixel.com.au>"
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
strategy: strategy:
fail-fast: true fail-fast: true
matrix: matrix:
php: ['8.1','8.2', '8.3'] php: ['8.2', '8.3', '8.4']
stability: [prefer-stable] stability: [prefer-stable]
steps: steps:
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) Pixel Digital Pty Ltd <hello@wearepixel.com.au> Copyright (c) Joel Male <joel@joelmale.com>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+361 -85
View File
@@ -1,49 +1,80 @@
[![Latest Version on Packagist](https://img.shields.io/packagist/v/wearepixel/laravel-cart.svg?style=flat-square)](https://packagist.org/packages/wearepixel/laravel-cart) <a name="top"></a>
[![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/wearepixel/laravel-cart/tests.yml?branch=master&label=Tests)](https://github.com/wearepixel/laravel-cart/actions?query=workflow%3ATests+branch%3Amaster) [![Laravel Cart](./docs/laravel-cart.png)](https://joelmale.com)
[![Total Downloads](https://img.shields.io/packagist/dt/wearepixel/laravel-cart.svg?style=flat-square)](https://packagist.org/packages/wearepixel/laravel-cart) [![Latest Version on Packagist](https://img.shields.io/packagist/v/joelwmale/laravel-cart.svg?style=flat-square)](https://packagist.org/packages/joelwmale/laravel-cart)
[![License](https://poser.pugx.org/wearepixel/laravel-cart/license.svg)](https://packagist.org/packages/wearepixel/laravel-cart) [![GitHub Tests Action Status](https://img.shields.io/github/actions/workflow/status/joelwmale/laravel-cart/tests.yml?branch=master&label=Tests)](https://github.com/joelwmale/laravel-cart/actions?query=workflow%3ATests+branch%3Amaster)
[![Total Downloads](https://img.shields.io/packagist/dt/joelwmale/laravel-cart.svg?style=flat-square)](https://packagist.org/packages/joelwmale/laravel-cart)
[![GitHub last commit](https://img.shields.io/github/last-commit/joelwmale/laravel-cart)](#)
[![License](https://poser.pugx.org/joelwmale/laravel-cart/license.svg)](https://packagist.org/packages/joelwmale/laravel-cart)
[![Free](https://img.shields.io/badge/free_for_non_commercial_use-brightgreen)](#-license)
A Cart Implementation for Laravel. A Cart Implementation for Laravel.
Supported Laravel Versions: 9, 10, 11 Supported Laravel Versions: 10, 11, and 12.
## Quick Demo For Laravel 9.0 and below, please use version [1.0](https://github.com/joelwmale/laravel-cart/releases/tag/1.0.12)
```php ## Table of Contents
// Add an item to the cart - [Getting Started](#-getting-started)
\Cart::add(1, 'Product 1', 19.99, 2, ['size' => 'large']); - [Demo](#-deni)
- [Documentation](#-documentation)
- [Configuration](#configuration)
- [Basic Usage](#basic-usage)
- [Conditions](#conditions)
- [Cart Items](#cart-items)
- [Database Support](#database-support)
- [Events](#events)
- [Credits](#-credits)
- [License](#-license)
// Get the cart contents ## 🚀 Getting Started
$cartContents = \Cart::getContent(); ### 🔥 Installing
// Update item quantity by its id
\Cart::update(1, ['quantity' => 3]);
// Remove an item from the cart by its id
\Cart::remove(1);
// Get cart total
\$total = Cart::getTotal();
// Clear the entire cart
\Cart::clear();
```
## Installing
Install the package through [Composer](http://getcomposer.org/). Install the package through [Composer](http://getcomposer.org/).
`composer require "wearepixel/laravel-cart"` `composer require joelwmale/laravel-cart`
## Configuration ## 🧑‍🍳 Demo
```php
// Add an item to the cart
\Cart::add(
1, // any unique id
'Product 1', // product name
19.99, // product price
2, // quantity
['size' => 'large'] // an array of extra attributes
);
// get the entire cart
$cartContents = \Cart::getContent();
// update an item already in the cart
\Cart::update(
1, // the same unique id that was used to add the item
['quantity' => 3] // the quantity to update
);
// remove the item by its id
\Cart::remove(1);
// get the total of the cart
$total = Cart::getTotal();
// clear it all when you've finished (like when you've stored the order)
\Cart::clear();
```
## 📚 Documentation
### ⚙️ Configuration
You can publish the configuration file to customize various options. You can publish the configuration file to customize various options.
```php ```php
php artisan vendor:publish --provider="Wearepixel\Cart\CartServiceProvider" --tag="config" php artisan vendor:publish --provider="Joelwmale\Cart\CartServiceProvider" --tag="config"
``` ```
### Formatting Numbers #### Formatting Numbers
The package by default does not use round to format numbers, and instead returns the number using floatval(). The package by default does not use round to format numbers, and instead returns the number using floatval().
@@ -56,7 +87,7 @@ ### Formatting Numbers
'round_mode' => env('LARAVEL_CART_ROUND_MODE', 'down'), 'round_mode' => env('LARAVEL_CART_ROUND_MODE', 'down'),
``` ```
### Decimals #### Decimals
You can customize the number of decimals in the configuration file. You can customize the number of decimals in the configuration file.
@@ -66,7 +97,7 @@ ### Decimals
'decimals' => env('LARAVEL_CART_DECIMALS', 2), 'decimals' => env('LARAVEL_CART_DECIMALS', 2),
``` ```
### Round Mode #### Round Mode
The package uses the `round` function to round the prices. You can customize the rounding mode in the configuration file. The package uses the `round` function to round the prices. You can customize the rounding mode in the configuration file.
@@ -76,21 +107,8 @@ ### Round Mode
'round_mode' => env('LARAVEL_CART_ROUND_MODE', 'down'), 'round_mode' => env('LARAVEL_CART_ROUND_MODE', 'down'),
``` ```
## HOW TO USE
- [Usage](#usage) ### Basic Usage
- [Conditions](#conditions)
- [Items](#items)
- [Associating Models](#associating-models)
- [Instances](#instances)
- [Exceptions](#exceptions)
- [Events](#events)
- [Format Response](#format)
- [Examples](#examples)
- [Using Different Storage](#storage)
- [License](#license)
## Usage
The cart has a default sessionKey that holds the cart data and stores it in the session. The cart has a default sessionKey that holds the cart data and stores it in the session.
@@ -109,7 +127,7 @@ ## Usage
\Cart::session(User::first()->id)->add(1, 'Product 1', 19.99, 2, ['size' => 'large']); \Cart::session(User::first()->id)->add(1, 'Product 1', 19.99, 2, ['size' => 'large']);
``` ```
### Adding to the cart: **Cart::add()** #### Adding to the cart: **Cart::add()**
There are a few ways to add items to the cart. There are a few ways to add items to the cart.
@@ -180,7 +198,7 @@ ### Adding to the cart: **Cart::add()**
); );
``` ```
### Updating an item on a cart: **Cart::update()** #### Updating an item on a cart: **Cart::update()**
```php ```php
Cart::update( Cart::update(
@@ -219,14 +237,14 @@ ### Updating an item on a cart: **Cart::update()**
); );
``` ```
### Removing an item on a cart: **Cart::remove()** #### Removing an item on a cart: **Cart::remove()**
```php ```php
// Remove an item from the cart by its id // Remove an item from the cart by its id
Cart::remove(456); Cart::remove(456);
``` ```
### Getting an item on a cart: **Cart::get()** #### Getting an item on a cart: **Cart::get()**
```php ```php
// Get an item from the cart by its id // Get an item from the cart by its id
@@ -236,7 +254,7 @@ ### Getting an item on a cart: **Cart::get()**
$summedPrice = Cart::get($itemId)->getPriceSum(); $summedPrice = Cart::get($itemId)->getPriceSum();
``` ```
### Getting the cart content: **Cart::getContent()** #### Getting the cart content: **Cart::getContent()**
```php ```php
// Returns a collection of the cart's contents // Returns a collection of the cart's contents
@@ -256,13 +274,13 @@ ### Getting the cart content: **Cart::getContent()**
$cartTotalQuantity = Cart::getTotalQuantity(); $cartTotalQuantity = Cart::getTotalQuantity();
``` ```
### Cart subtotal: **Cart::getSubTotal()** #### Cart subtotal: **Cart::getSubTotal()**
```php ```php
$subTotal = Cart::getSubTotal(); $subTotal = Cart::getSubTotal();
``` ```
### Cart subtotal without conditions: **Cart::getSubTotalWithoutConditions()** #### Cart subtotal without conditions: **Cart::getSubTotalWithoutConditions()**
```php ```php
$subTotalWithoutConditions = Cart::getSubTotalWithoutConditions(); $subTotalWithoutConditions = Cart::getSubTotalWithoutConditions();
@@ -274,35 +292,47 @@ ### Cart subtotal without conditions: **Cart::getSubTotalWithoutConditions()**
$total = Cart::getTotal(); $total = Cart::getTotal();
``` ```
### Check if cart is empty: **Cart::isEmpty()** #### Check if cart is empty: **Cart::isEmpty()**
```php ```php
Cart::isEmpty(); Cart::isEmpty();
``` ```
### Clearing the Cart: **Cart::clear()** #### Clearing the Cart: **Cart::clear()**
This clears all items and conditions from the cart.
```php ```php
Cart::clear(); Cart::clear();
``` ```
## Conditions #### Clearing the cart items only: **Cart::clearItems()**
Conditions are very useful in terms for adding things like discounts, taxes, shipping costs, etc. This clears all items, but keeps the conditions (useful for when you want to keep the conditions but remove the items)
Conditions can be added on the entire cart or on individual items. ```php
Cart::clearItems();
```
### Conditions
Conditions are very useful for adding things like discounts, taxes, shipping, etc.
Conditions can be added on the entire cart or on individual items, and can even be applied only at certain cart values.
Conditions on a cart level should always have a `target` of `subtotal` or `total`. This tells the cart which value to apply the condition to. Conditions on a cart level should always have a `target` of `subtotal` or `total`. This tells the cart which value to apply the condition to.
You can provide an optional `minimum` value which should be the dollar value in which the target (subtotal or total) needs to be for the condition to be active and impact the cart.
You can also provide an `order` to cart conditions which tells the cart in what order to apply the conditions. Item level conditions do not support the `order` parameter. You can also provide an `order` to cart conditions which tells the cart in what order to apply the conditions. Item level conditions do not support the `order` parameter.
### Conditions on the cart #### Conditions on the cart
#### Adding a condition to the cart: **Cart::condition()** ##### Adding a condition to the cart: **Cart::condition()**
```php ```php
// Add a single condition to the cart // Add a single condition to the cart
$condition = new \Wearepixel\Cart\CartCondition([ $condition = new \Joelwmale\Cart\CartCondition([
'name' => 'Tax: 10%', 'name' => 'Tax: 10%',
'type' => 'tax', 'type' => 'tax',
'target' => 'subtotal', // this condition will be applied to cart's subtotal when getSubTotal() is called. 'target' => 'subtotal', // this condition will be applied to cart's subtotal when getSubTotal() is called.
@@ -315,7 +345,7 @@ #### Adding a condition to the cart: **Cart::condition()**
Cart::condition($condition); Cart::condition($condition);
// Add multiple conditions // Add multiple conditions
$tax = new \Wearepixel\Cart\CartCondition([ $tax = new \Joelwmale\Cart\CartCondition([
'name' => 'Tax: 10%', 'name' => 'Tax: 10%',
'type' => 'tax', 'type' => 'tax',
'target' => 'subtotal', // this condition will be applied to cart's subtotal when getSubTotal() is called. 'target' => 'subtotal', // this condition will be applied to cart's subtotal when getSubTotal() is called.
@@ -323,7 +353,7 @@ #### Adding a condition to the cart: **Cart::condition()**
'order' => 2 'order' => 2
]); ]);
$shipping = new \Wearepixel\Cart\CartCondition([ $shipping = new \Joelwmale\Cart\CartCondition([
'name' => 'Shipping: $15', 'name' => 'Shipping: $15',
'type' => 'shipping', 'type' => 'shipping',
'target' => 'subtotal', // this condition will be applied to cart's subtotal when getSubTotal() is called. 'target' => 'subtotal', // this condition will be applied to cart's subtotal when getSubTotal() is called.
@@ -338,7 +368,7 @@ #### Adding a condition to the cart: **Cart::condition()**
Cart::condition([$tax, $shipping]); Cart::condition([$tax, $shipping]);
// add condition to only apply on totals, not in subtotal // add condition to only apply on totals, not in subtotal
$shipping = new \Wearepixel\Cart\CartCondition([ $shipping = new \Joelwmale\Cart\CartCondition([
'name' => 'Express Shipping $15', 'name' => 'Express Shipping $15',
'type' => 'shipping', 'type' => 'shipping',
'target' => 'total', 'target' => 'total',
@@ -349,7 +379,7 @@ #### Adding a condition to the cart: **Cart::condition()**
Cart::condition($shipping); Cart::condition($shipping);
``` ```
#### Getting conditions on the cart: **Cart::getConditions()** ##### Getting conditions on the cart: **Cart::getConditions()**
```php ```php
// To get all applied conditions on a cart, use below: // To get all applied conditions on a cart, use below:
@@ -362,34 +392,97 @@ #### Getting conditions on the cart: **Cart::getConditions()**
$condition->getType(); // the type $condition->getType(); // the type
$condition->getValue(); // the value of the condition $condition->getValue(); // the value of the condition
$condition->getOrder(); // the order of the condition $condition->getOrder(); // the order of the condition
$condition->getMinimum(); // the minimum dollar amount of the target, needed to activate the condition
$condition->getMaximum(); // the maximum dollar amount of the target, needed to keep the condition active
$condition->getAttributes(); // the attributes of the condition, returns an empty [] if no attributes added $condition->getAttributes(); // the attributes of the condition, returns an empty [] if no attributes added
} }
```
##### Getting conditions on the cart as an array: **Cart::getConditions(array: true)**
You can get all cart conditions in array format by passing "array: true". This is useful if you want to store the carts conditions on a Livewire component since by default we have collections inside collections for conditions which Livewire does not support.
```php
$cartConditions = Cart::getConditions(true);
$cartConditions = Cart::getConditions(active: true);
foreach ($cartConditions as $condition) {
$condition['name']; // the name of the condition
$condition['type']; // the type
$condition['value']; // the value of the condition
$condition['order']; // the order of the condition
$condition['minimum']; // the minimum dollar amount of the target, needed to activate the condition
$condition['maximum']; // the maximum dollar amount of the target, needed to keep the condition active
$condition['attributes']; // the attributes of the condition, returns an empty [] if no attributes added
}
```
##### Getting conditions by name: **Cart::getCondition($conditionName)**
```php
$condition = Cart::getCondition('GST');
// You can also get a condition that has been applied on the cart by using its name:
$condition = Cart::getCondition('Tax: 10%');
$condition->getTarget(); // the target of which the condition was applied $condition->getTarget(); // the target of which the condition was applied
$condition->getName(); // the name of the condition $condition->getName(); // the name of the condition
$condition->getType(); // the type $condition->getType(); // the type
$condition->getValue(); // the value of the condition $condition->getValue(); // the value of the condition
$condition->getMinimum(); // the minimum dollar amount of the target, needed to activate the condition
$condition->getMaximum(); // the maximum dollar amount of the target, needed to keep the condition active
$condition->getAttributes(); // the attributes of the condition, returns an empty [] if no attributes added $condition->getAttributes(); // the attributes of the condition, returns an empty [] if no attributes added
``` ```
### Calculating condition value ##### Getting active conditions on the cart: **Cart::getConditions(active: true)**
You can get only active conditions by passsing `active: true` to the `getConditions()` method.
This will return conditions that are actively being applied to the cart (i.e if they meet their minimum or maximum value)
```php
$tenPercentOff = new CartCondition([
'name' => '10% Off',
'type' => 'discount',
'target' => 'subtotal',
'value' => '-10%',
'minimum' => 120,
'order' => 1,
]);
Cart::getConditions(active: true);
// will return "10% Off" if the subtotal of the cart is $200.
// will return no conditions if the subtotal is $100.
$shipping = new CartCondition([
'name' => 'Shipping',
'type' => 'discount',
'target' => 'subtotal',
'value' => '10',
'maximum' => 200,
'order' => 1,
]);
Cart::getConditions(active: true);
// will return "Shipping" if the subtotal of the cart is less than or equal 200
// will return no conditions if the subtotal is $210
```
##### Calculating condition value
There are 2 ways to calculate the value of a condition: There are 2 ways to calculate the value of a condition:
1. Using the `getCalculatedValue` method on the condition instance 1. Using the `getCalculatedValue` method on the condition instance
2. Using the `getCalculatedValueForCondition` method on the cart instance and passing the condition name 2. Using the `getCalculatedValueForCondition` method on the cart instance and passing the condition name
#### Using the `getCalculatedValue` method on the condition instance ##### Using the `getCalculatedValue` method on the condition instance
```php ```php
$subTotal = Cart::getSubTotal(); $subTotal = Cart::getSubTotal();
$condition = Cart::getCondition('VAT 12.5%'); $condition = Cart::getCondition('10% GST');
$conditionCalculatedValue = $condition->getCalculatedValue($subTotal); $conditionCalculatedValue = $condition->getCalculatedValue($subTotal);
``` ```
#### Using the `getCalculatedValueForCondition` method on the cart instance ##### Using the `getCalculatedValueForCondition` method on the cart instance
This method automatically calculates the value of a condition by it's name based on the order of the conditions. This method automatically calculates the value of a condition by it's name based on the order of the conditions.
@@ -422,14 +515,52 @@ #### Using the `getCalculatedValueForCondition` method on the cart instance
Cart::getCalculatedValueForCondition('Gift Card'); // returns 0 as the coupon discount is applied first and brings the subtotal to 0 Cart::getCalculatedValueForCondition('Gift Card'); // returns 0 as the coupon discount is applied first and brings the subtotal to 0
``` ```
### Condition on items ##### Adding conditions that activate once a minimum value is met
You can add a `minimum` amount required for a condition to activate.
This is useful for applying discounts only after certain cart values, i.e: 10% off for any purchases over $120.
```php
$tenPercentOff = new CartCondition([
'name' => '10% Off',
'type' => 'discount',
'target' => 'subtotal',
'value' => '-10%',
'minimum' => 120,
'order' => 1,
]);
Cart::condition($tenPercentOff)
```
##### Adding conditions that only activate up to a maximum value
You can add a `maximum` amount required for a condition to activate.
This is useful for applying discounts up until amounts, i.e shipping for anything below $200, and free shipping above.
```php
$shipping = new CartCondition([
'name' => 'Shipping',
'type' => 'discount',
'target' => 'subtotal',
'value' => '12',
'maximum' => 200,
'order' => 1,
]);
Cart::condition($shipping)
```
#### Conditions on items
Item conditions are useful if you have discounts to be applied specifically on an item and not on the whole cart value. Item conditions are useful if you have discounts to be applied specifically on an item and not on the whole cart value.
```php ```php
// lets create first our condition instance // lets create first our condition instance
$saleCondition = new \Wearepixel\Cart\CartCondition([ $saleCondition = new \Joelwmale\Cart\CartCondition([
'name' => '50% Off', 'name' => '50% Off',
'type' => 'tax', 'type' => 'tax',
'value' => '-50%', 'value' => '-50%',
@@ -449,7 +580,7 @@ ### Condition on items
Cart::add($product); Cart::add($product);
// You can of course also do multiple conditions on an item // You can of course also do multiple conditions on an item
$saleCondition = new \Wearepixel\Cart\CartCondition([ $saleCondition = new \Joelwmale\Cart\CartCondition([
'name' => 'SALE 5%', 'name' => 'SALE 5%',
'type' => 'sale', 'type' => 'sale',
'value' => '-5%', 'value' => '-5%',
@@ -483,7 +614,7 @@ ### Condition on items
$cartSubTotal = Cart::getSubTotal(); $cartSubTotal = Cart::getSubTotal();
``` ```
#### Add condition to existing Item on the cart: **Cart::addItemCondition($productId, $itemCondition)** ##### Add a condition to an existing item on the cart: **Cart::addItemCondition($productId, $itemCondition)**
Adding Condition to an existing Item on the cart is simple as well. Adding Condition to an existing Item on the cart is simple as well.
@@ -497,7 +628,7 @@ #### Add condition to existing Item on the cart: **Cart::addItemCondition($produ
Cart::addItemCondition(456, $condition); Cart::addItemCondition(456, $condition);
``` ```
### Clearing Cart Conditions: **Cart::clearCartConditions()** #### Clearing Cart Conditions: **Cart::clearCartConditions()**
This clears all cart level conditions, and does not affect item level conditions. This clears all cart level conditions, and does not affect item level conditions.
@@ -511,25 +642,25 @@ ### Clearing Cart Conditions: **Cart::clearCartConditions()**
Cart::clearAllConditions() Cart::clearAllConditions()
``` ```
### Remove a specific cart condtion: **Cart::removeCartCondition($conditionName)** ##### Remove a specific cart condtion: **Cart::removeCartCondition($conditionName)**
```php ```php
Cart::removeCartCondition('Summer Sale 5%') Cart::removeCartCondition('Summer Sale 5%')
``` ```
### Remove a specific item condition: **Cart::removeItemCondition($itemId, $conditionName)** ##### Remove a specific item condition: **Cart::removeItemCondition($itemId, $conditionName)**
```php ```php
Cart::removeItemCondition(456, 'SALE 5%') Cart::removeItemCondition(456, 'SALE 5%')
``` ```
### Clear all item conditions: **Cart::clearItemConditions($itemId)** ##### Clear all item conditions: **Cart::clearItemConditions($itemId)**
```php ```php
Cart::clearItemConditions(456) Cart::clearItemConditions(456)
``` ```
### Get conditions by type: **Cart::getConditionsByType($type)** #### Get conditions by type: **Cart::getConditionsByType($type)**
This returns all conditions that has been added to the cart by the type specified. This returns all conditions that has been added to the cart by the type specified.
@@ -537,13 +668,13 @@ ### Get conditions by type: **Cart::getConditionsByType($type)**
$tax = Cart::getConditionsByType('tax'); $tax = Cart::getConditionsByType('tax');
``` ```
### Remove conditions by type: **Cart::removeConditionsByType($type)** ##### Remove conditions by type: **Cart::removeConditionsByType($type)**
```php ```php
Cart::removeConditionsByType('tax'); Cart::removeConditionsByType('tax');
``` ```
## Cart Items ### Cart Items
The method **Cart::getContent()** returns a collection of items. The method **Cart::getContent()** returns a collection of items.
@@ -565,8 +696,153 @@ ## Cart Items
$item->getPriceSumWithConditions(); $item->getPriceSumWithConditions();
``` ```
## Credits ### Storage Options
Huge credits to the original maintainer of the package, located at [darryldecode/laravelshoppingcart](https://github.com/darryldecode/laravelshoppingcart) By default the cart is stored in the session, but there are times you may want to store the cart in the database.
We have forked this package to update it to Laravel 11 and to add some features and support for newer PHP versions. For instance, you may want to store the cart in the database so that the cart can be retrieved even after the user logs out or closes the browser, or you may want to add cart timeouts and support for multiple computers.
#### Session
The cart is stored in the session by default, using Laravel's in-built SessionManager.
#### Database Support
To get started, you'll need to create a new model for your Cart, the package requires only 3 columns, but you're free to extend this as you wish and add more columns.
You can utilise the [events](#events) provided by the package to store additional information alongside the cart.
```php
$table->string('session_id'); // this handles the session id of the cart
$table->text('items'); // this will store the cart items
$table->text('conditions'); // this will store the cart level conditions
```
Then add some json casts to your model and fillable columns:
```php
protected $guarded = [];
protected $casts = [
'items' => 'array',
'conditions' => 'array',
];
```
Then update the configuration file to use the database driver:
```php
'driver' => 'database',
'storage' => [
'session',
'database' => [
'model' => \App\Models\Cart::class, // your model here
'id' => 'session_id',
'items' => 'items',
'conditions' => 'conditions',
],
],
```
### Events
The package provides a few events that you can listen to in order to manipulate the cart or take actions based on cart events.
#### LaravelCart.Created
This fires every time a cart is instantiated (i.e every time \Cart::add() is called)
```
Event::listen('LaravelCart.Added', function () {
// cart was created
});
```
#### LaravelCart.Adding
This fires every time an item is being added to the cart
```
Event::listen('LaravelCart.Adding', function ($item) {
// item is being added
});
```
#### LaravelCart.Added
This fires every time an item is successfully added to the cart
```
Event::listen('LaravelCart.Added', function ($item) {
// item was added
});
```
#### LaravelCart.Updating
This fires every time an item is being updated
```
Event::listen('LaravelCart.Updating', function ($item) {
// item is being updated
});
```
#### LaravelCart.Updated
This fires every time an item is successfully updated
```
Event::listen('LaravelCart.Updated', function ($item) {
// item was updated
});
```
#### LaravelCart.Removing
This fires every time an item is being removed
```
Event::listen('LaravelCart.Removing', function ($item) {
// item is being removed
});
```
#### LaravelCart.Removed
This fires every time an item is successfully removed
```
Event::listen('LaravelCart.Removed', function ($item) {
// item was removed
});
```
#### LaravelCart.Clearing
This fires every time the cart is being cleared
```
Event::listen('LaravelCart.Clearing', function () {
// cart is being cleared
});
```
#### LaravelCart.Cleared
This fires every time the cart is successfully cleared
```
Event::listen('LaravelCart.Cleared', function () {
// cart was cleared
});
```
## 🫡 Credits
This package was orignally created by [darryldecode](https://github.com/darryldecode) but has since seen almost no updates. I have decided to take the old package and transform it into a new package with new features and updates.
## 📓 License
The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
+20 -13
View File
@@ -1,6 +1,6 @@
{ {
"name": "wearepixel/laravel-cart", "name": "joelwmale/laravel-cart",
"version": "1.0.8", "version": "1.2.0",
"description": "A cart implementation for Laravel", "description": "A cart implementation for Laravel",
"keywords": [ "keywords": [
"laravel", "laravel",
@@ -15,34 +15,41 @@
}, },
{ {
"name": "Joel Male", "name": "Joel Male",
"email": "joel@wearepixel.com.au" "email": "joel@joelmale.com"
} }
], ],
"require": { "require": {
"php": ">=8.1", "php": ">=8.2",
"illuminate/support": "^9.0|^10.0|^11.0", "illuminate/support": "^10.0|^11.0|^12.0",
"illuminate/validation": "^9.0|^10.0|^11.0", "illuminate/auth": "^10.0|^11.0|^12.0",
"illuminate/translation": "^9.0|^10.0|^11.0" "illuminate/container": "^10.0|^11.0|^12.0",
"illuminate/database": "^10.0|^11.0|^12.0",
"illuminate/validation": "^10.0|^11.0|^12.0"
}, },
"require-dev": { "require-dev": {
"mockery/mockery": "^1.4.2", "mockery/mockery": "^1.6",
"laravel/pint": "dev-main", "laravel/pint": "^1.21",
"pestphp/pest": "2.35.1", "pestphp/pest": "^3.0",
"larapack/dd": "dev-master" "larapack/dd": "dev-master"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Wearepixel\\Cart\\": "src/" "Joelwmale\\Cart\\": "src/"
} }
}, },
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"laravel": { "laravel": {
"providers": [ "providers": [
"Wearepixel\\Cart\\CartServiceProvider" "Joelwmale\\Cart\\CartServiceProvider"
], ],
"aliases": { "aliases": {
"Cart": "Wearepixel\\Cart\\Facades\\CartFacade" "Cart": "Joelwmale\\Cart\\Facades\\CartFacade"
} }
} }
}, },
Generated
+3513 -1384
View File
File diff suppressed because it is too large Load Diff
+11 -8
View File
@@ -14,15 +14,18 @@
/* /*
* --------------------------------------------------------------- * ---------------------------------------------------------------
* Persistence * Storage
* --------------------------------------------------------------- * ---------------------------------------------------------------
*/ */
'storage' => null, 'driver' => 'session',
/* 'storage' => [
* --------------------------------------------------------------- 'session',
* Events 'database' => [
* --------------------------------------------------------------- 'model' => '',
*/ 'id' => '',
'events' => null, 'items' => '',
'conditions' => '',
],
],
]; ];
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+3
View File
@@ -14,4 +14,7 @@
<directory>tests</directory> <directory>tests</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<php>
<env name="DB_CONNECTION" value="testing"/>
</php>
</phpunit> </phpunit>
+147 -160
View File
@@ -1,100 +1,59 @@
<?php <?php
namespace Wearepixel\Cart; namespace Joelwmale\Cart;
use Wearepixel\Cart\Helpers\Helpers; use Joelwmale\Cart\Helpers\Helpers;
use Wearepixel\Cart\Validators\CartItemValidator; use Joelwmale\Cart\Validators\CartItemValidator;
use Wearepixel\Cart\Exceptions\InvalidItemException; use Joelwmale\Cart\Exceptions\InvalidItemException;
use Wearepixel\Cart\Exceptions\UnknownModelException; use Joelwmale\Cart\Exceptions\UnknownModelException;
use Wearepixel\Cart\Exceptions\InvalidConditionException; use Joelwmale\Cart\Exceptions\InvalidConditionException;
class Cart class Cart
{ {
/**
* the item storage
*/
protected $session; protected $session;
/**
* the event dispatcher
*/
protected $events; protected $events;
/**
* the cart session key
*/
protected $instanceName; protected $instanceName;
/**
* the session key use for the cart
*/
protected $sessionKey;
/**
* the session key use to persist cart items
*/
protected $sessionKeyCartItems;
/**
* the session key use to persist cart conditions
*/
protected $sessionKeyCartConditions;
/**
* Configuration to pass to ItemCollection
*/
protected $config; protected $config;
/**
* This holds the currently added item id in cart for association
*/
protected $currentItemId; protected $currentItemId;
/**
* The number of decimals to use when formatting prices
*/
protected $decimals; protected $decimals;
/**
* The decimal point character
*/
protected $decPoint; protected $decPoint;
/** public function __construct($sessionStorage, $events, $instanceName, $sessionKey, $config)
* our object constructor
*/
public function __construct($session, $events, $instanceName, $session_key, $config)
{ {
$this->validateConfig($config);
$this->events = $events; $this->events = $events;
$this->session = $session; $this->session = new CartSession($sessionStorage, $sessionKey, $config);
$this->instanceName = $instanceName; $this->instanceName = $instanceName;
$this->sessionKey = $session_key;
$this->sessionKeyCartItems = $this->sessionKey . '_cart_items';
$this->sessionKeyCartConditions = $this->sessionKey . '_cart_conditions';
$this->config = $config; $this->config = $config;
$this->currentItemId = null; $this->currentItemId = null;
$this->fireEvent('created');
$this->fireEvent('Created');
} }
/** private function validateConfig($config)
* sets the session key
*
* @param string $sessionKey the session key or identifier
* @return $this|bool
*
* @throws \Exception
*/
public function session($sessionKey)
{ {
if (! $sessionKey) { if ($config['driver'] == 'database') {
throw new \Exception('Session key is required.'); if (! isset($config['storage']['database']['model'])) {
throw new \Exception('You need to set a model for the database driver in the cart configuration.');
}
if (! isset($config['storage']['database']['id'])) {
throw new \Exception('You need to set an id (for the session) for the database driver in the cart configuration.');
}
if (! isset($config['storage']['database']['items'])) {
throw new \Exception('You need to set an items key for the database driver in the cart configuration.');
}
if (! isset($config['storage']['database']['conditions'])) {
throw new \Exception('You need to set a conditions key for the database driver in the cart configuration.');
}
} }
$this->sessionKey = $sessionKey;
$this->sessionKeyCartItems = $this->sessionKey . '_cart_items';
$this->sessionKeyCartConditions = $this->sessionKey . '_cart_conditions';
return $this;
} }
/** /**
@@ -109,20 +68,16 @@ public function getInstanceName()
/** /**
* get an item on a cart by item ID * get an item on a cart by item ID
*
* @return mixed
*/ */
public function get($itemId) public function get($itemId): ?ItemCollection
{ {
return $this->getContent()->get($itemId); return $this->getContent()->get($itemId);
} }
/** /**
* check if an item exists by item ID * check if we have this item in the cart by item ID
*
* @return bool
*/ */
public function has($itemId) public function has($itemId): bool
{ {
return $this->getContent()->has($itemId); return $this->getContent()->has($itemId);
} }
@@ -144,10 +99,9 @@ public function add(
array|CartCondition $conditions = [], array|CartCondition $conditions = [],
?string $associatedModel = null ?string $associatedModel = null
): self { ): self {
// check if its an array of items // check if we are adding an array of items
if (is_array($id)) { if (is_array($id)) {
// the first argument is an array, now we will need to check if it is a multi dimensional // check if we are adding a multi dimensional array
// array, if so, we will iterate through each item and call add again
if (Helpers::isMultiArray($id)) { if (Helpers::isMultiArray($id)) {
foreach ($id as $item) { foreach ($id as $item) {
$this->add( $this->add(
@@ -175,6 +129,7 @@ public function add(
return $this; return $this;
} }
// if we are here, we are adding a single item
$data = [ $data = [
'id' => $id, 'id' => $id,
'name' => $name, 'name' => $name,
@@ -191,11 +146,8 @@ public function add(
// validate data // validate data
$item = $this->validate($data); $item = $this->validate($data);
// get the cart
$inCart = $this->get($id);
// if the item is already in the cart we will just update it // if the item is already in the cart we will just update it
if ($inCart) { if ($this->get($id)) {
$this->update($id, $item); $this->update($id, $item);
} else { } else {
$this->addItem($id, $item); $this->addItem($id, $item);
@@ -211,7 +163,7 @@ public function add(
*/ */
public function update(int|string $id, array $data): bool public function update(int|string $id, array $data): bool
{ {
if ($this->fireEvent('updating', $data) === false) { if ($this->fireEvent('Updating', 'item', $data) === false) {
return false; return false;
} }
@@ -244,7 +196,7 @@ public function update(int|string $id, array $data): bool
$this->save($cart); $this->save($cart);
$this->fireEvent('updated', $item); $this->fireEvent('Updated', 'item', $item);
return true; return true;
} }
@@ -270,11 +222,11 @@ public function setConfig($config)
public function addItemCondition($productId, $itemCondition) public function addItemCondition($productId, $itemCondition)
{ {
if ($product = $this->get($productId)) { if ($product = $this->get($productId)) {
$conditionInstance = '\\Wearepixel\\Cart\\CartCondition'; $conditionInstance = '\\Joelwmale\\Cart\\CartCondition';
if ($itemCondition instanceof $conditionInstance) { if ($itemCondition instanceof $conditionInstance) {
// we need to copy first to a temporary variable to hold the conditions // we need to copy first to a temporary variable to hold the conditions
// to avoid hitting this error "Indirect modification of overloaded element of Wearepixel\Cart\ItemCollection has no effect" // to avoid hitting this error "Indirect modification of overloaded element of Joelwmale\Cart\ItemCollection has no effect"
// this is due to laravel Collection instance that implements Array Access // this is due to laravel Collection instance that implements Array Access
// // see link for more info: http://stackoverflow.com/questions/20053269/indirect-modification-of-overloaded-element-of-splfixedarray-has-no-effect // // see link for more info: http://stackoverflow.com/questions/20053269/indirect-modification-of-overloaded-element-of-splfixedarray-has-no-effect
$itemConditionTempHolder = $product['conditions']; $itemConditionTempHolder = $product['conditions'];
@@ -303,7 +255,7 @@ public function remove($id)
{ {
$cart = $this->getContent(); $cart = $this->getContent();
if ($this->fireEvent('removing', $id) === false) { if ($this->fireEvent('Removing', 'item', $id) === false) {
return false; return false;
} }
@@ -311,32 +263,40 @@ public function remove($id)
$this->save($cart); $this->save($cart);
$this->fireEvent('removed', $id); $this->fireEvent('Removed', 'item', $id);
return true;
}
public function clear()
{
if ($this->fireEvent('Clearing') === false) {
return false;
}
$this->session->clear();
$this->fireEvent('Cleared');
return true;
}
public function clearItems()
{
if ($this->fireEvent('Clearing') === false) {
return false;
}
$this->session->clearItems();
$this->fireEvent('Cleared');
return true; return true;
} }
/** /**
* clear cart * Adds a condition to the cart
*
* @return bool
*/ */
public function clear()
{
if ($this->fireEvent('clearing') === false) {
return false;
}
$this->session->put(
$this->sessionKeyCartItems,
[]
);
$this->fireEvent('cleared');
return true;
}
/** /**
* Adds a condition to the cart * Adds a condition to the cart
* *
@@ -353,7 +313,7 @@ public function condition(array|CartCondition $condition): self
} }
if (! $condition instanceof CartCondition) { if (! $condition instanceof CartCondition) {
throw new InvalidConditionException('Argument 1 must be an instance of \'Wearepixel\Cart\CartCondition\''); throw new InvalidConditionException('Argument 1 must be an instance of \'Joelwmale\Cart\CartCondition\'');
} }
$conditions = $this->getConditions(); $conditions = $this->getConditions();
@@ -366,7 +326,9 @@ public function condition(array|CartCondition $condition): self
$conditions->put($condition->getName(), $condition); $conditions->put($condition->getName(), $condition);
$conditions = $conditions->sortBy(function ($condition, $key) { $conditions = $conditions->reject(function ($condition) {
return empty($condition);
})->sortBy(function ($condition, $key) {
return $condition->getOrder(); return $condition->getOrder();
}); });
@@ -378,11 +340,34 @@ public function condition(array|CartCondition $condition): self
/** /**
* get conditions applied on the cart * get conditions applied on the cart
* *
* @return CartConditionCollection * @param bool $active - If true, only conditions actually applied on the cart are returned
*/ */
public function getConditions() public function getConditions(bool $array = false, bool $active = false): CartConditionCollection|array
{ {
return new CartConditionCollection($this->session->get($this->sessionKeyCartConditions)); $conditions = new CartConditionCollection($this->session->getConditions());
$conditions = $conditions->reject(function ($condition) {
return empty($condition);
})
->transform(function ($condition) {
return $condition instanceof CartCondition ? $condition : new CartCondition($condition);
});
if ($active) {
return $conditions->filter(function (CartCondition $condition) {
$amount = $condition->getTarget() == 'subtotal' ? $this->getSubTotal(false) : $this->getTotal();
return $condition->getMinimum() === null || $condition->getMinimum() <= $amount;
})->filter(function (CartCondition $condition) {
$amount = $condition->getTarget() == 'subtotal' ? $this->getSubTotal(false) : $this->getTotal();
return $condition->getMaximum() === null || $condition->getMaximum() >= $amount;
});
}
return $array ? $conditions->map(function (CartCondition $condition) {
return $condition->toArray();
})->toArray() : $conditions;
} }
/** /**
@@ -411,12 +396,8 @@ public function getConditionsByType($type)
/** /**
* Remove all the condition with the $type specified * Remove all the condition with the $type specified
* Please Note that this will only remove condition added on cart bases, not those conditions added
* specifically on an per item bases
*
* @return $this
*/ */
public function removeConditionsByType($type) public function removeConditionsByType(string $type)
{ {
$this->getConditionsByType($type)->each(function ($condition) { $this->getConditionsByType($type)->each(function ($condition) {
$this->removeCartCondition($condition->getName()); $this->removeCartCondition($condition->getName());
@@ -424,12 +405,7 @@ public function removeConditionsByType($type)
} }
/** /**
* removes a condition on a cart by condition name, * Remove a condition from the cart
* this can only remove conditions that are added on cart bases not conditions that are added on an item/product.
* If you wish to remove a condition that has been added for a specific item/product, you may
* use the removeItemCondition(itemId, conditionName) method instead.
*
* @return void
*/ */
public function removeCartCondition($conditionName) public function removeCartCondition($conditionName)
{ {
@@ -479,7 +455,7 @@ public function removeItemCondition($itemId, $conditionName)
// on the given condition name the user wants to remove, if so, // on the given condition name the user wants to remove, if so,
// lets just make $item['conditions'] an empty array as there's just 1 condition on it anyway // lets just make $item['conditions'] an empty array as there's just 1 condition on it anyway
else { else {
$conditionInstance = 'Wearepixel\\Cart\\CartCondition'; $conditionInstance = 'Joelwmale\\Cart\\CartCondition';
if ($item['conditions'] instanceof $conditionInstance) { if ($item['conditions'] instanceof $conditionInstance) {
if ($tempConditionsHolder->getName() == $conditionName) { if ($tempConditionsHolder->getName() == $conditionName) {
@@ -519,10 +495,7 @@ public function clearItemConditions($itemId)
*/ */
public function clearCartConditions(): void public function clearCartConditions(): void
{ {
$this->session->put( $this->session->putConditions([]);
$this->sessionKeyCartConditions,
[]
);
} }
/** /**
@@ -556,14 +529,10 @@ public function getSubTotalWithoutConditions($formatted = true)
return Helpers::formatValue(floatval($sum), $formatted, $this->config); return Helpers::formatValue(floatval($sum), $formatted, $this->config);
} }
/**
* get cart sub total
*
* @param bool $formatted
*/
public function getSubTotal($formatted = true): int|float|string public function getSubTotal($formatted = true): int|float|string
{ {
$subTotal = 0.00; $subTotal = 0.00;
// add all the items together with conditions applied // add all the items together with conditions applied
$subTotalSum = $this->getContent()->sum(function (ItemCollection $item) { $subTotalSum = $this->getContent()->sum(function (ItemCollection $item) {
return $item->getPriceSumWithConditions(false); return $item->getPriceSumWithConditions(false);
@@ -575,6 +544,12 @@ public function getSubTotal($formatted = true): int|float|string
->filter(function (CartCondition $condition) { ->filter(function (CartCondition $condition) {
return $condition->getTarget() === 'subtotal'; return $condition->getTarget() === 'subtotal';
}) })
->filter(function (CartCondition $condition) use ($subTotalSum) {
return $condition->getMinimum() === null || $condition->getMinimum() <= $subTotalSum;
})
->filter(function (CartCondition $condition) use ($subTotalSum) {
return $condition->getMaximum() === null || $condition->getMaximum() >= $subTotalSum;
})
->sortBy(function (CartCondition $condition) { ->sortBy(function (CartCondition $condition) {
return $condition->getOrder(); return $condition->getOrder();
}); });
@@ -607,6 +582,15 @@ public function getTotal(): int|float
$conditionsForTotal = $this->getConditions() $conditionsForTotal = $this->getConditions()
->filter(function (CartCondition $condition) { ->filter(function (CartCondition $condition) {
return $condition->getTarget() === 'total'; return $condition->getTarget() === 'total';
})
->filter(function (CartCondition $condition) use ($subTotal) {
return $condition->getMinimum() === null || $condition->getMinimum() <= $subTotal;
})
->filter(function (CartCondition $condition) use ($subTotal) {
return $condition->getMaximum() === null || $condition->getMaximum() >= $subTotal;
})
->sortBy(function (CartCondition $condition) {
return $condition->getOrder();
}); });
// if no conditions were added, just return the sub total // if no conditions were added, just return the sub total
@@ -614,10 +598,14 @@ public function getTotal(): int|float
return Helpers::formatValue($subTotal, $this->config['format_numbers'], $this->config); return Helpers::formatValue($subTotal, $this->config['format_numbers'], $this->config);
} }
$conditionsForTotal->each(function (CartCondition $condition) use ($subTotal, &$total) { $index = 0;
$toBeCalculated = $total > 0 ? $total : $subTotal;
$conditionsForTotal->each(function (CartCondition $condition) use ($subTotal, &$total, &$index) {
$toBeCalculated = ($index > 0) ? $total : $subTotal;
$total = $condition->applyCondition($toBeCalculated); $total = $condition->applyCondition($toBeCalculated);
$index++;
}); });
return Helpers::formatValue($total, $this->config['format_numbers'], $this->config); return Helpers::formatValue($total, $this->config['format_numbers'], $this->config);
@@ -665,24 +653,16 @@ public function getTotalQuantity()
return $count; return $count;
} }
/** public function getContent(): CartCollection
* get the cart
*
* @return CartCollection
*/
public function getContent()
{ {
return (new CartCollection($this->session->get($this->sessionKeyCartItems)))->reject(function ($item) { return (new CartCollection($this->session->getItems()))->transform(function ($item) {
return $item instanceof ItemCollection ? $item : new ItemCollection($item, $this->config);
})->reject(function ($item) {
return ! ($item instanceof ItemCollection); return ! ($item instanceof ItemCollection);
}); });
} }
/** public function isEmpty($test = null)
* check if cart is empty
*
* @return bool
*/
public function isEmpty()
{ {
return $this->getContent()->isEmpty(); return $this->getContent()->isEmpty();
} }
@@ -728,7 +708,7 @@ protected function validate($item)
*/ */
protected function addItem($id, $item) protected function addItem($id, $item)
{ {
if ($this->fireEvent('adding', $item) === false) { if ($this->fireEvent('Adding', 'item', $item) === false) {
return false; return false;
} }
@@ -738,7 +718,7 @@ protected function addItem($id, $item)
$this->save($cart); $this->save($cart);
$this->fireEvent('added', $item); $this->fireEvent('Added', 'item', $item);
return true; return true;
} }
@@ -750,7 +730,7 @@ protected function addItem($id, $item)
*/ */
protected function save($cart) protected function save($cart)
{ {
$this->session->put($this->sessionKeyCartItems, $cart); $this->session->putItems($cart);
} }
/** /**
@@ -758,7 +738,7 @@ protected function save($cart)
*/ */
protected function saveConditions($conditions) protected function saveConditions($conditions)
{ {
$this->session->put($this->sessionKeyCartConditions, $conditions); $this->session->putConditions($conditions);
} }
/** /**
@@ -776,7 +756,7 @@ protected function itemHasConditions($item)
return count($item['conditions']) > 0; return count($item['conditions']) > 0;
} }
$conditionInstance = 'Wearepixel\\Cart\\CartCondition'; $conditionInstance = 'Joelwmale\\Cart\\CartCondition';
if ($item['conditions'] instanceof $conditionInstance) { if ($item['conditions'] instanceof $conditionInstance) {
return true; return true;
@@ -829,12 +809,19 @@ public function setDecimals($decimals)
$this->decimals = $decimals; $this->decimals = $decimals;
} }
/** protected function fireEvent($name, $type = null, $data = null)
* @return mixed
*/
protected function fireEvent($name, $value = [])
{ {
return $this->events->dispatch($this->getInstanceName() . '.' . $name, array_values([$value, $this]), true); $eventData = [
'session_key' => $this->session->sessionKey,
'session_driver' => $this->session->driver,
'session_model' => $this->session->getSessionModel(),
];
if ($type) {
$eventData[$type] = $data;
}
return $this->events->dispatch('LaravelCart.' . $name, array_values([$eventData, $this]), true);
} }
/** /**
+1 -1
View File
@@ -1,6 +1,6 @@
<?php <?php
namespace Wearepixel\Cart; namespace Joelwmale\Cart;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
+26 -8
View File
@@ -1,22 +1,19 @@
<?php <?php
namespace Wearepixel\Cart; namespace Joelwmale\Cart;
use Wearepixel\Cart\Helpers\Helpers; use Joelwmale\Cart\Helpers\Helpers;
use Wearepixel\Cart\Validators\CartConditionValidator; use Joelwmale\Cart\Validators\CartConditionValidator;
use Wearepixel\Cart\Exceptions\InvalidConditionException; use Joelwmale\Cart\Exceptions\InvalidConditionException;
class CartCondition class CartCondition
{ {
/**
* @var array
*/
private $args; private $args;
/** /**
* the parsed raw value of the condition * the parsed raw value of the condition
*/ */
public $parsedRawValue = 0; private $parsedRawValue = 0;
/** /**
* @param array $args (name, type, target, value) * @param array $args (name, type, target, value)
@@ -65,6 +62,22 @@ public function getType()
return $this->args['type']; return $this->args['type'];
} }
/**
* Get the minimum value of this condition
*/
public function getMinimum(): int|float|null
{
return $this->args['minimum'] ?? null;
}
/**
* Get the maximum value of this condition
*/
public function getMaximum(): int|float|null
{
return $this->args['maximum'] ?? null;
}
/** /**
* get the additional attributes of a condition * get the additional attributes of a condition
* *
@@ -253,4 +266,9 @@ protected function validate($args)
throw new InvalidConditionException($validator->messages()->first()); throw new InvalidConditionException($validator->messages()->first());
} }
} }
public function toArray(): array
{
return $this->args;
}
} }
+1 -1
View File
@@ -1,6 +1,6 @@
<?php <?php
namespace Wearepixel\Cart; namespace Joelwmale\Cart;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
+9 -14
View File
@@ -1,6 +1,6 @@
<?php <?php
namespace Wearepixel\Cart; namespace Joelwmale\Cart;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@@ -35,24 +35,19 @@ public function register()
$this->mergeConfigFrom(__DIR__ . '/../config/config.php', 'cart'); $this->mergeConfigFrom(__DIR__ . '/../config/config.php', 'cart');
$this->app->singleton('cart', function ($app) { $this->app->singleton('cart', function ($app) {
$storageClass = config('cart.storage'); $config = config('cart');
$eventsClass = config('cart.events'); $events = $app['events'];
$storage = $storageClass ? new $storageClass : $app['session']; $storage = $config['driver'] === 'database'
$events = $eventsClass ? new $eventsClass : $app['events']; ? new $config['storage']['database']['model']
$instanceName = 'cart'; : $app['session'];
// default session or cart identifier. This will be overridden when calling Cart::session($sessionKey)->add() etc..
// like when adding a cart for a specific user name. Session Key can be string or maybe a unique identifier to bind a cart
// to a specific user, this can also be a user ID
$session_key = '4yTlTDKu3oJOfzD';
return new Cart( return new Cart(
$storage, $storage,
$events, $events,
$instanceName, 'cart',
$session_key, session()->getId(),
config('cart') $config
); );
}); });
} }
+146
View File
@@ -0,0 +1,146 @@
<?php
namespace Joelwmale\Cart;
class CartSession
{
private $customStorage;
public $sessionKey;
public $driver;
private $session;
private $sessionId = null;
private $itemsKey = null;
private $conditionsKey = null;
public function __construct($session, $sessionKey, $config)
{
$this->sessionKey = $sessionKey;
$this->driver = $config['driver'];
if ($this->driver === 'session') {
$this->customStorage = false;
$this->session = $session;
$this->itemsKey = $this->sessionKey . '_cart_items';
$this->conditionsKey = $this->sessionKey . '_cart_conditions';
} elseif ($this->driver === 'database') {
$this->customStorage = true;
$this->sessionId = $config['storage']['database']['id'];
$this->itemsKey = $config['storage']['database']['items'];
$this->conditionsKey = $config['storage']['database']['conditions'];
$this->session = $session->firstOrNew([$this->sessionId => $this->sessionKey]);
$this->session[$this->itemsKey] = $this->session[$this->itemsKey] ?? [];
$this->session[$this->conditionsKey] = $this->session[$this->conditionsKey] ?? [];
}
}
public function isInternalSession($session)
{
return in_array(is_object($session) ? get_class($session) : $session, [
'Illuminate\Session\SessionManager',
'Joelwmale\Cart\Tests\Helpers\SessionMock',
]);
}
public function has($key)
{
return $this->session->has($key) ?? $this->session;
}
public function getItems()
{
return $this->driver == 'session' ? $this->session->get($this->itemsKey) : $this->session[$this->itemsKey];
}
public function getConditions()
{
return $this->driver == 'session' ? $this->session->get($this->conditionsKey) : $this->session[$this->conditionsKey];
}
public function putItems($value)
{
if ($this->driver == 'session') {
return $this->session->put($this->itemsKey, $value);
}
// convert any conditions to arrays
$value = is_array($value) ? $value : $value->toArray();
foreach ($value as $key => $item) {
if ($item['conditions']) {
if (is_array($item['conditions'])) {
$value[$key]['conditions'] = collect($item['conditions'])->map(function ($condition) {
return $condition->toArray();
});
} else {
$value[$key]['conditions'] = $item['conditions']->toArray();
}
}
}
$this->session[$this->itemsKey] = $value;
$this->session->save();
return $this->session;
}
public function putConditions($value)
{
if ($this->driver == 'session') {
return $this->session->put($this->conditionsKey, $value);
}
if (! empty($value)) {
$value = $value->mapWithKeys(function ($condition) {
return [$condition->getName() => $condition->toArray()];
});
}
$this->session[$this->conditionsKey] = $value;
$this->session->save();
return $this->session;
}
public function clearItems()
{
if ($this->driver == 'session') {
$this->session->forget($this->itemsKey);
return true;
}
$this->session[$this->itemsKey] = [];
return $this->session->save();
}
public function clear()
{
if ($this->driver == 'session') {
$this->session->forget($this->itemsKey);
$this->session->forget($this->conditionsKey);
return true;
}
$deleted = $this->session->delete();
$this->session = [
$this->itemsKey => [],
$this->conditionsKey => [],
];
return $deleted;
}
public function getSessionModel()
{
return $this->session;
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
<?php <?php
namespace Wearepixel\Cart\Exceptions; namespace Joelwmale\Cart\Exceptions;
class InvalidConditionException extends \Exception {} class InvalidConditionException extends \Exception {}
+1 -1
View File
@@ -1,5 +1,5 @@
<?php <?php
namespace Wearepixel\Cart\Exceptions; namespace Joelwmale\Cart\Exceptions;
class InvalidItemException extends \Exception {} class InvalidItemException extends \Exception {}
+1 -1
View File
@@ -1,5 +1,5 @@
<?php <?php
namespace Wearepixel\Cart\Exceptions; namespace Joelwmale\Cart\Exceptions;
class UnknownModelException extends \Exception {} class UnknownModelException extends \Exception {}
+1 -1
View File
@@ -1,6 +1,6 @@
<?php <?php
namespace Wearepixel\Cart\Facades; namespace Joelwmale\Cart\Facades;
use Illuminate\Support\Facades\Facade; use Illuminate\Support\Facades\Facade;
+1 -1
View File
@@ -1,6 +1,6 @@
<?php <?php
namespace Wearepixel\Cart\Helpers; namespace Joelwmale\Cart\Helpers;
class Helpers class Helpers
{ {
+1 -1
View File
@@ -1,6 +1,6 @@
<?php <?php
namespace Wearepixel\Cart; namespace Joelwmale\Cart;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
+37 -7
View File
@@ -1,9 +1,9 @@
<?php <?php
namespace Wearepixel\Cart; namespace Joelwmale\Cart;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Wearepixel\Cart\Helpers\Helpers; use Joelwmale\Cart\Helpers\Helpers;
class ItemCollection extends Collection class ItemCollection extends Collection
{ {
@@ -72,7 +72,7 @@ public function hasConditions()
if (is_array($this['conditions'])) { if (is_array($this['conditions'])) {
return count($this['conditions']) > 0; return count($this['conditions']) > 0;
} }
$conditionInstance = 'Wearepixel\\Cart\\CartCondition'; $conditionInstance = 'Joelwmale\\Cart\\CartCondition';
if ($this['conditions'] instanceof $conditionInstance) { if ($this['conditions'] instanceof $conditionInstance) {
return true; return true;
} }
@@ -91,7 +91,37 @@ public function getConditions()
return []; return [];
} }
return $this['conditions']; $conditionsArray = [];
$conditions = $this['conditions'];
// check if we're already an array of CartConditions
foreach ($conditions as $key => $condition) {
if ($condition instanceof CartCondition) {
return $conditions;
}
}
$hasSubArray = false;
foreach ($conditions as $key => $condition) {
if (is_array($condition) && ! $condition instanceof CartCondition) {
$hasSubArray = true;
$conditionsArray[] = new CartCondition($condition);
} elseif ($condition instanceof CartCondition) {
$conditionsArray[] = $condition;
}
}
if (! $hasSubArray) {
if ($conditions instanceof CartCondition) {
return $conditions;
}
$conditionsArray[] = new CartCondition($conditions);
}
return $conditionsArray;
} }
/** /**
@@ -108,14 +138,14 @@ public function getPriceWithConditions($formatted = true)
$processed = 0; $processed = 0;
if ($this->hasConditions()) { if ($this->hasConditions()) {
if (is_array($this->conditions)) { if (is_array($this->getConditions())) {
foreach ($this->conditions as $condition) { foreach ($this->getConditions() as $condition) {
($processed > 0) ? $toBeCalculated = $newPrice : $toBeCalculated = $originalPrice; ($processed > 0) ? $toBeCalculated = $newPrice : $toBeCalculated = $originalPrice;
$newPrice = $condition->applyCondition($toBeCalculated); $newPrice = $condition->applyCondition($toBeCalculated);
$processed++; $processed++;
} }
} else { } else {
$newPrice = $this['conditions']->applyCondition($originalPrice); $newPrice = $this->getConditions()->applyCondition($originalPrice);
} }
return Helpers::formatValue($newPrice, $formatted, $this->config); return Helpers::formatValue($newPrice, $formatted, $this->config);
+1 -1
View File
@@ -1,5 +1,5 @@
<?php <?php
namespace Wearepixel\Cart\Validators; namespace Joelwmale\Cart\Validators;
class CartConditionValidator extends Validator {} class CartConditionValidator extends Validator {}
+1 -1
View File
@@ -1,5 +1,5 @@
<?php <?php
namespace Wearepixel\Cart\Validators; namespace Joelwmale\Cart\Validators;
class CartItemValidator extends Validator {} class CartItemValidator extends Validator {}
+1 -1
View File
@@ -1,6 +1,6 @@
<?php <?php
namespace Wearepixel\Cart\Validators; namespace Joelwmale\Cart\Validators;
use Illuminate\Validation\Factory; use Illuminate\Validation\Factory;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
+1 -2
View File
@@ -4,6 +4,5 @@
'format_numbers' => true, 'format_numbers' => true,
'decimals' => 2, 'decimals' => 2,
'dec_point' => '.', 'dec_point' => '.',
'thousands_sep' => ',', 'driver' => 'session',
'use_cents' => false,
]; ];
+17
View File
@@ -0,0 +1,17 @@
<?php
return [
'format_numbers' => true,
'decimals' => 2,
'round_mode' => 'up',
'driver' => 'database',
'storage' => [
'session',
'database' => [
'model' => \Joelwmale\Cart\Tests\Helpers\MockCartModel::class,
'id' => 'session_id',
'items' => 'items',
'conditions' => 'conditions',
],
],
];
+1
View File
@@ -4,4 +4,5 @@
'format_numbers' => true, 'format_numbers' => true,
'decimals' => 2, 'decimals' => 2,
'round_mode' => 'up', 'round_mode' => 'up',
'driver' => 'session',
]; ];
+1 -1
View File
@@ -4,5 +4,5 @@
'format_numbers' => false, 'format_numbers' => false,
'decimals' => 3, 'decimals' => 3,
'dec_point' => ',', 'dec_point' => ',',
'thousands_sep' => '.', 'driver' => 'session',
]; ];
+23
View File
@@ -0,0 +1,23 @@
<?php
namespace Joelwmale\Cart\Tests\Helpers;
use Illuminate\Database\Eloquent\Model;
class MockCartModel extends Model
{
protected $fillable = [
'session_id',
'items',
'conditions',
];
public $timestamps = false;
protected $casts = [
'items' => 'array',
'conditions' => 'array',
];
protected $table = 'carts';
}
+1 -1
View File
@@ -1,6 +1,6 @@
<?php <?php
namespace Wearepixel\Cart\Tests\Helpers; namespace Joelwmale\Cart\Tests\Helpers;
use Illuminate\Contracts\Database\ModelIdentifier; use Illuminate\Contracts\Database\ModelIdentifier;
+6 -1
View File
@@ -1,6 +1,6 @@
<?php <?php
namespace Wearepixel\Cart\Tests\Helpers; namespace Joelwmale\Cart\Tests\Helpers;
class SessionMock class SessionMock
{ {
@@ -20,4 +20,9 @@ public function put($key, $value)
{ {
$this->session[$key] = $value; $this->session[$key] = $value;
} }
public function forget($key)
{
unset($this->session[$key]);
}
} }
+2 -26
View File
@@ -1,29 +1,5 @@
<?php <?php
/* use Tests\TestCase;
|--------------------------------------------------------------------------
| Test Case
|--------------------------------------------------------------------------
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
| need to change it using the "pest()" function to bind a different classes or traits.
|
*/
// pest()->extend(Tests\TestCase::class)->in('Feature'); uses(TestCase::class)->in(__DIR__);
/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/
expect()->extend('toBeOne', function () {
return $this->toBe(1);
});
+44 -2
View File
@@ -1,10 +1,52 @@
<?php <?php
declare(strict_types=1);
namespace Tests; namespace Tests;
use Illuminate\Events\Dispatcher;
use Illuminate\Container\Container;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Capsule\Manager as DB;
use PHPUnit\Framework\TestCase as BaseTestCase; use PHPUnit\Framework\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase class TestCase extends BaseTestCase
{ {
// protected DB $db;
protected function setUp(): void
{
parent::setUp();
// Create a Laravel container
$container = Container::getInstance();
$dispatcher = new Dispatcher;
// Setup Capsule (Eloquent ORM)
$this->db = new DB($container);
$this->db->addConnection([
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
]);
$this->db->setAsGlobal();
$this->db->bootEloquent();
$this->db->getConnection()->setEventDispatcher($dispatcher);
// Run migrations (optional, for testing models)
$this->migrateDatabase();
}
protected function migrateDatabase()
{
$schema = $this->db->schema();
$schema->create('carts', function (Blueprint $table) {
$table->id();
$table->string('session_id');
$table->text('items');
$table->text('conditions');
$table->timestamps();
});
}
} }
+433 -182
View File
@@ -1,8 +1,9 @@
<?php <?php
use Wearepixel\Cart\Cart; use Joelwmale\Cart\Cart;
use Wearepixel\Cart\CartCondition; use Joelwmale\Cart\CartCondition;
use Wearepixel\Cart\Tests\Helpers\SessionMock; use Joelwmale\Cart\CartConditionCollection;
use Joelwmale\Cart\Tests\Helpers\SessionMock;
beforeEach(function () { beforeEach(function () {
$events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher'); $events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher');
@@ -21,7 +22,168 @@
Mockery::close(); Mockery::close();
}); });
describe('conditions', function () {
test('can be completely cleared', function () {
$siteWideDiscount = new CartCondition([
'name' => 'Site Wide Discount',
'type' => 'discount',
'target' => 'total',
'value' => '-5%',
]);
$giftCard = new CartCondition([
'name' => 'Gift Card',
'type' => 'gift_card',
'target' => 'total',
'value' => '-25',
]);
$itemCondition = new CartCondition([
'name' => 'Item Discount',
'type' => 'discount',
'value' => '-5%',
]);
$item = [
'id' => 1,
'name' => 'Backpack',
'price' => 168.72,
'quantity' => 1,
'attributes' => [],
'conditions' => [$itemCondition],
];
$this->cart->add($item);
$this->cart->condition([$siteWideDiscount, $giftCard]);
expect($this->cart->getConditions()->count())->toEqual(2, 'Cart should have two conditions');
expect($this->cart->get(1)['conditions'])->toHaveCount(1, 'Cart should have one condition');
$this->cart->clearAllConditions();
expect($this->cart->getConditions()->count())->toEqual(0, 'Cart should have no conditions now');
expect($this->cart->get(1)['conditions'])->toHaveCount(0, 'Cart items should have no conditions now');
});
test('can be converted to array', function () {
$condition = new CartCondition([
'name' => 'Site Wide Discount',
'type' => 'discount',
'target' => 'total',
'value' => '-5%',
]);
$this->cart->condition($condition);
$conditions = $this->cart->getConditions(true);
expect($conditions)->toHaveCount(1, 'Cart should have one condition');
expect($conditions['Site Wide Discount'])->toEqual([
'name' => 'Site Wide Discount',
'type' => 'discount',
'target' => 'total',
'value' => '-5%',
'order' => 1,
]);
});
test('can be removed by name', function () {
$conditionOne = new CartCondition([
'name' => '5% Site Wide Sale',
'type' => 'sale',
'value' => '-5%',
]);
$conditionTwo = new CartCondition([
'name' => 'Mother\'s Day Gift Pack',
'type' => 'promo',
'value' => '-25',
]);
$this->cart->add([
'id' => 1,
'name' => 'Body Scrub Kit',
'price' => 100,
'quantity' => 1,
'attributes' => [],
]);
$this->cart->condition([$conditionOne, $conditionTwo]);
// let's very first the item has 2 conditions in it
expect($this->cart->getConditions())->toHaveCount(2, 'The item should have two conditions');
// now let's remove a condition on that item using the condition name
$this->cart->removeCartCondition('5% Site Wide Sale');
// now we should have only 1 condition left on that item
expect($this->cart->getConditions())->toHaveCount(1, 'Item should have one condition left');
expect($this->cart->getConditions()->first()->getName())->toEqual('Mother\'s Day Gift Pack');
});
});
describe('cart level conditions', function () { describe('cart level conditions', function () {
test('can get conditions', function () {
$this->cart->add([
'id' => 456,
'name' => 'Leather Shoes',
'price' => 129.99,
'quantity' => 1,
]);
$condition = new CartCondition([
'name' => '$5 Discount',
'type' => 'tax',
'target' => 'subtotal',
'value' => '-5',
]);
$this->cart->condition($condition);
$conditions = $this->cart->getConditions();
expect($conditions)->toHaveCount(1, 'Cart should have one condition');
expect($conditions)->toBeInstanceOf(CartConditionCollection::class);
});
test('can get conditions by name', function () {
$this->cart->add([
'id' => 456,
'name' => 'Leather Shoes',
'price' => 129.99,
'quantity' => 1,
]);
$conditionOne = new CartCondition([
'name' => '$5 Discount',
'type' => 'tax',
'target' => 'subtotal',
'value' => '-5',
]);
$conditionTwo = new CartCondition([
'name' => '5% Off',
'type' => 'tax',
'target' => 'subtotal',
'value' => '-5%',
]);
$this->cart->condition($conditionOne);
$this->cart->condition($conditionTwo);
$conditions = $this->cart->getConditions();
expect($conditions)->toHaveCount(2, 'Cart should have two conditions');
expect($conditions)->toBeInstanceOf(CartConditionCollection::class);
$condition = $this->cart->getCondition('5% Off');
expect($condition)->toBeInstanceOf(CartCondition::class);
expect($condition->getName())->toEqual('5% Off');
expect($condition->getValue())->toEqual('-5%');
});
test('can discount the subtotal by dollar amount', function () { test('can discount the subtotal by dollar amount', function () {
$this->cart->add([ $this->cart->add([
'id' => 456, 'id' => 456,
@@ -792,40 +954,6 @@
expect($giftCard->getCalculatedValue())->toEqual(0, 'Gift Card value should be 0'); expect($giftCard->getCalculatedValue())->toEqual(0, 'Gift Card value should be 0');
}); });
test('can be cleared', function () {
$siteWideDiscount = new CartCondition([
'name' => 'Site Wide Discount',
'type' => 'discount',
'target' => 'total',
'value' => '-5%',
]);
$giftCard = new CartCondition([
'name' => 'Gift Card',
'type' => 'gift_card',
'target' => 'total',
'value' => '-25',
]);
$item = [
'id' => 1,
'name' => 'Backpack',
'price' => 168.72,
'quantity' => 1,
'attributes' => [],
];
$this->cart->add($item);
$this->cart->condition([$siteWideDiscount, $giftCard]);
expect($this->cart->getConditions()->count())->toEqual(2, 'Cart should have two conditions');
$this->cart->clearCartConditions();
expect($this->cart->getConditions()->count())->toEqual(0, 'Cart should have no conditions now');
});
test('calculate the subtotal correctly when one condition makes it $0, and the other adds $10', function () { test('calculate the subtotal correctly when one condition makes it $0, and the other adds $10', function () {
$this->cart->add([ $this->cart->add([
[ [
@@ -915,10 +1043,218 @@
expect($this->cart->getSubTotal())->toEqual(10.00, 'Cart should have sub total of 10.00'); expect($this->cart->getSubTotal())->toEqual(10.00, 'Cart should have sub total of 10.00');
expect($this->cart->getTotal())->toEqual(10.00, 'Cart should have a total of 10.00'); expect($this->cart->getTotal())->toEqual(10.00, 'Cart should have a total of 10.00');
}); });
test('can activate a dollar discount when a minimum amount is met', function () {
$condition = new CartCondition([
'name' => 'Get $10 off when you spend $100',
'type' => 'target',
'target' => 'total',
'value' => '-10',
'minimum' => '100',
]);
$this->cart->add([
'id' => 1,
'name' => 'Apple iPhone 15',
'price' => 90,
'quantity' => 1,
'attributes' => [],
]);
$this->cart->condition($condition);
expect($this->cart->getTotal())->toEqual(90, 'Cart should have total of 90');
$this->cart->clearItems();
$this->cart->add([
'id' => 1,
'name' => 'Apple iPhone 15',
'price' => 90,
'quantity' => 2,
'attributes' => [],
]);
expect($this->cart->getTotal())->toEqual(170, 'Cart should have total of 170');
});
test('can activate a percentage discount when a minimum amount is met', function () {
$condition = new CartCondition([
'name' => 'Get 10% off when you spend $100',
'type' => 'target',
'target' => 'total',
'value' => '-10%',
'minimum' => '100',
]);
$this->cart->add([
'id' => 1,
'name' => 'Apple iPhone 15',
'price' => 90,
'quantity' => 1,
'attributes' => [],
]);
$this->cart->condition($condition);
expect($this->cart->getTotal())->toEqual(90, 'Cart should have total of 90');
$this->cart->clearItems();
$this->cart->add([
'id' => 1,
'name' => 'Apple iPhone 15',
'price' => 90,
'quantity' => 2,
'attributes' => [],
]);
expect($this->cart->getTotal())->toEqual(162, 'Cart should have total of 162');
});
test('can remove a dollar value condition when a maximum is met', function () {
$condition = new CartCondition([
'name' => 'Shipping',
'type' => 'target',
'target' => 'total',
'value' => '18',
'maximum' => '160',
]);
$this->cart->add([
'id' => 1,
'name' => 'Apple iPhone 15',
'price' => 90,
'quantity' => 1,
'attributes' => [],
]);
$this->cart->condition($condition);
expect($this->cart->getTotal())->toEqual(108, 'Cart should have total of 108');
$this->cart->clear();
$this->cart->add([
'id' => 1,
'name' => 'Apple iPhone 15',
'price' => 90,
'quantity' => 2,
'attributes' => [],
]);
expect($this->cart->getTotal())->toEqual(180, 'Cart should have total of 180');
});
test('can remove a percentage condition when a maximum is met', function () {
$condition = new CartCondition([
'name' => 'Low Cart Tax',
'type' => 'target',
'target' => 'total',
'value' => '10%',
'maximum' => '140',
]);
$this->cart->add([
'id' => 1,
'name' => 'Apple iPhone 15',
'price' => 90,
'quantity' => 1,
'attributes' => [],
]);
$this->cart->condition($condition);
expect($this->cart->getTotal())->toEqual(99, 'Cart should have total of 99');
$this->cart->clear();
$this->cart->add([
'id' => 1,
'name' => 'Apple iPhone 15',
'price' => 90,
'quantity' => 2,
'attributes' => [],
]);
expect($this->cart->getTotal())->toEqual(180, 'Cart should have total of 180');
});
test('return cart conditions only when they are active', function () {
$condition = new CartCondition([
'name' => 'Get $10 off when you spend $100',
'type' => 'target',
'target' => 'total',
'value' => '-10',
'minimum' => '100',
]);
$this->cart->add([
'id' => 1,
'name' => 'Apple iPhone 15',
'price' => 90,
'quantity' => 1,
'attributes' => [],
]);
$this->cart->condition($condition);
expect($this->cart->getConditions(active: true))->toHaveCount(0, 'Cart should have 0 conditions');
$this->cart->clearItems();
$this->cart->add([
'id' => 1,
'name' => 'Apple iPhone 15',
'price' => 90,
'quantity' => 2,
'attributes' => [],
]);
expect($this->cart->getConditions(active: true))->toHaveCount(1, 'Cart should have 1 condition');
});
test('can be removed by condition name', function () {
$shipping = new CartCondition([
'name' => 'Shipping',
'type' => 'shipping',
'target' => 'total',
'value' => '12',
]);
$tax = new CartCondition([
'name' => 'Tax',
'type' => 'tax',
'target' => 'total',
'value' => '2.50',
]);
$item = [
'id' => 456,
'name' => 'Sample Item 1',
'price' => 100,
'quantity' => 1,
'attributes' => [],
];
$this->cart->add($item);
$this->cart->condition([$shipping, $tax]);
// let's prove first we have now two conditions in the cart
expect($this->cart->getConditions()->count())->toEqual(2, 'Cart should have two conditions');
// now let's remove a specific condition by condition name
$this->cart->removeCartCondition('Tax');
// cart should have now only 1 condition
expect($this->cart->getConditions()->count())->toEqual(1, 'Cart should have one condition');
expect($this->cart->getConditions()->first()->getName())->toEqual('Shipping');
});
}); });
describe('item level conditions', function () { describe('item level conditions', function () {
test('can add an item with a condition', function () { test('can be added to an item', function () {
$saleCondition = new CartCondition([ $saleCondition = new CartCondition([
'name' => '5% Discount', 'name' => '5% Discount',
'type' => 'sale', 'type' => 'sale',
@@ -942,6 +1278,41 @@
expect($this->cart->getTotal())->toEqual(18, 'Cart should have total of 18'); expect($this->cart->getTotal())->toEqual(18, 'Cart should have total of 18');
}); });
test('can be removed by name', function () {
$itemCondition1 = new CartCondition([
'name' => '5% Site Wide Sale',
'type' => 'sale',
'value' => '-5%',
]);
$itemCondition2 = new CartCondition([
'name' => 'Mother\'s Day Gift Pack',
'type' => 'promo',
'value' => '-25',
]);
$item = [
'id' => 1,
'name' => 'Body Scrub Kit',
'price' => 100,
'quantity' => 1,
'attributes' => [],
'conditions' => [$itemCondition1, $itemCondition2],
];
$this->cart->add($item);
// let's very first the item has 2 conditions in it
expect($this->cart->get(1)['conditions'])->toHaveCount(2, 'The item should have two conditions');
// now let's remove a condition on that item using the condition name
$this->cart->removeItemCondition(1, '5% Site Wide Sale');
// now we should have only 1 condition left on that item
expect($this->cart->get(1)['conditions'])->toHaveCount(1, 'Item should have one condition left');
expect($this->cart->get(1)['conditions'][1]->getName())->toEqual('Mother\'s Day Gift Pack');
});
test('can add item with multiple negative item conditions', function () { test('can add item with multiple negative item conditions', function () {
$saleCondition = new CartCondition([ $saleCondition = new CartCondition([
'name' => '5% Sale', 'name' => '5% Sale',
@@ -1082,110 +1453,36 @@
expect($this->cart->get(1)->getPriceSumWithConditions())->toEqual(0.00, "The item's price should be prevented from going below zero."); expect($this->cart->get(1)->getPriceSumWithConditions())->toEqual(0.00, "The item's price should be prevented from going below zero.");
}); });
// test('get cart condition by condition name', function () { test('can be removed by condition name', function () {
// $itemCondition1 = new CartCondition([ $heavyItemSurcharge = new CartCondition([
// 'name' => 'SALE 5%', 'name' => 'Heavy Item Surcharge',
// 'type' => 'sale', 'type' => 'surcharge',
// 'target' => 'total', 'value' => '10',
// 'value' => '-5%', ]);
// ]);
// $itemCondition2 = new CartCondition([
// 'name' => 'Item Gift Pack 25.00',
// 'type' => 'promo',
// 'target' => 'total',
// 'value' => '-25',
// ]);
// $item = [ $tax = new CartCondition([
// 'id' => 456, 'name' => 'Tax',
// 'name' => 'Sample Item 1', 'type' => 'tax',
// 'price' => 100, 'value' => '10%',
// 'quantity' => 1, ]);
// 'attributes' => [],
// ];
// $this->cart->add($item); $item = [
'id' => 12,
'name' => 'Beer (1 box)',
'price' => 410.60,
'quantity' => 1,
'attributes' => [],
'conditions' => [$heavyItemSurcharge, $tax],
];
// $this->cart->condition([$itemCondition1, $itemCondition2]); $this->cart->add($item);
// // get a condition applied on cart by condition name expect($this->cart->get(12)['conditions'])->toHaveCount(2, 'Item should have two conditions');
// $condition = $this->cart->getCondition($itemCondition1->getName());
// expect('SALE 5%')->toEqual($condition->getName()); $this->cart->removeItemCondition(12, 'Tax');
// expect('total')->toEqual($condition->getTarget());
// expect('sale')->toEqual($condition->getType());
// expect('-5%')->toEqual($condition->getValue());
// });
// test('remove cart condition by condition name', function () { expect($this->cart->get(12)['conditions'])->toHaveCount(1, 'Item should have one condition');
// $itemCondition1 = new CartCondition([ });
// 'name' => 'SALE 5%',
// 'type' => 'sale',
// 'target' => 'total',
// 'value' => '-5%',
// ]);
// $itemCondition2 = new CartCondition([
// 'name' => 'Item Gift Pack 25.00',
// 'type' => 'promo',
// 'target' => 'total',
// 'value' => '-25',
// ]);
// $item = [
// 'id' => 456,
// 'name' => 'Sample Item 1',
// 'price' => 100,
// 'quantity' => 1,
// 'attributes' => [],
// ];
// $this->cart->add($item);
// $this->cart->condition([$itemCondition1, $itemCondition2]);
// // let's prove first we have now two conditions in the cart
// expect($this->cart->getConditions()->count())->toEqual(2, 'Cart should have two conditions');
// // now let's remove a specific condition by condition name
// $this->cart->removeCartCondition('SALE 5%');
// // cart should have now only 1 condition
// expect($this->cart->getConditions()->count())->toEqual(1, 'Cart should have one condition');
// expect($this->cart->getConditions()->first()->getName())->toEqual('Item Gift Pack 25.00');
// });
// test('remove item condition by condition name', function () {
// $itemCondition1 = new CartCondition([
// 'name' => 'SALE 5%',
// 'type' => 'sale',
// 'value' => '-5%',
// ]);
// $itemCondition2 = new CartCondition([
// 'name' => 'Item Gift Pack 25.00',
// 'type' => 'promo',
// 'value' => '-25',
// ]);
// $item = [
// 'id' => 456,
// 'name' => 'Sample Item 1',
// 'price' => 100,
// 'quantity' => 1,
// 'attributes' => [],
// 'conditions' => [$itemCondition1, $itemCondition2],
// ];
// $this->cart->add($item);
// // let's very first the item has 2 conditions in it
// expect($this->cart->get(456)['conditions'])->toHaveCount(2, 'Item should have two conditions');
// // now let's remove a condition on that item using the condition name
// $this->cart->removeItemCondition(456, 'SALE 5%');
// // now we should have only 1 condition left on that item
// expect($this->cart->get(456)['conditions'])->toHaveCount(1, 'Item should have one condition left');
// });
// test('remove item condition by condition name scenario two', function () { // test('remove item condition by condition name scenario two', function () {
// // NOTE: in this scenario, we will add the conditions not in array format // // NOTE: in this scenario, we will add the conditions not in array format
@@ -1556,50 +1853,4 @@
// expect($this->cart->getConditions()->first()->getName())->toEqual('Item Gift Pack 20'); // expect($this->cart->getConditions()->first()->getName())->toEqual('Item Gift Pack 20');
// expect($this->cart->getConditions()->last()->getName())->toEqual('TAX'); // expect($this->cart->getConditions()->last()->getName())->toEqual('TAX');
// }); // });
});
describe('conditions', function () {
test('can be completely cleared', function () {
$siteWideDiscount = new CartCondition([
'name' => 'Site Wide Discount',
'type' => 'discount',
'target' => 'total',
'value' => '-5%',
]);
$giftCard = new CartCondition([
'name' => 'Gift Card',
'type' => 'gift_card',
'target' => 'total',
'value' => '-25',
]);
$itemCondition = new CartCondition([
'name' => 'Item Discount',
'type' => 'discount',
'value' => '-5%',
]);
$item = [
'id' => 1,
'name' => 'Backpack',
'price' => 168.72,
'quantity' => 1,
'attributes' => [],
'conditions' => [$itemCondition],
];
$this->cart->add($item);
$this->cart->condition([$siteWideDiscount, $giftCard]);
expect($this->cart->getConditions()->count())->toEqual(2, 'Cart should have two conditions');
expect($this->cart->get(1)['conditions'])->toHaveCount(1, 'Cart should have one condition');
$this->cart->clearAllConditions();
expect($this->cart->getConditions()->count())->toEqual(0, 'Cart should have no conditions now');
expect($this->cart->get(1)['conditions'])->toHaveCount(0, 'Cart items should have no conditions now');
});
}); });
+10 -10
View File
@@ -1,8 +1,8 @@
<?php <?php
use Wearepixel\Cart\Cart; use Joelwmale\Cart\Cart;
use Wearepixel\Cart\Tests\Helpers\MockProduct; use Joelwmale\Cart\Tests\Helpers\MockProduct;
use Wearepixel\Cart\Tests\Helpers\SessionMock; use Joelwmale\Cart\Tests\Helpers\SessionMock;
beforeEach(function () { beforeEach(function () {
$events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher'); $events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher');
@@ -149,7 +149,7 @@
$item = $this->cart->get(1); $item = $this->cart->get(1);
expect($item->attributes)->toBeInstanceOf('Wearepixel\\Cart\\ItemAttributeCollection'); expect($item->attributes)->toBeInstanceOf('Joelwmale\\Cart\\ItemAttributeCollection');
// now lets update the item with its new attributes // now lets update the item with its new attributes
// when we get that item from cart, it should still be an instance of ItemAttributeCollection // when we get that item from cart, it should still be an instance of ItemAttributeCollection
@@ -162,7 +162,7 @@
$this->cart->update(1, $updatedItem); $this->cart->update(1, $updatedItem);
expect($item->attributes)->toBeInstanceOf('Wearepixel\\Cart\\ItemAttributeCollection'); expect($item->attributes)->toBeInstanceOf('Joelwmale\\Cart\\ItemAttributeCollection');
}); });
test('item orders are preserved by the order they are added', function () { test('item orders are preserved by the order they are added', function () {
@@ -298,19 +298,19 @@
}); });
test('should throw an exception when no quantity is provided', function () { test('should throw an exception when no quantity is provided', function () {
$this->expectException('Wearepixel\Cart\Exceptions\InvalidItemException'); $this->expectException('Joelwmale\Cart\Exceptions\InvalidItemException');
$this->expectExceptionMessage('The quantity must be at least 0.1.'); $this->expectExceptionMessage('The quantity must be at least 0.1.');
$this->cart->add(455, 'Sample Item', 100.99, 0, []); $this->cart->add(455, 'Sample Item', 100.99, 0, []);
}); });
test('should throw an exception when no id is provided', function () { test('should throw an exception when no id is provided', function () {
$this->expectException('Wearepixel\Cart\Exceptions\InvalidItemException'); $this->expectException('Joelwmale\Cart\Exceptions\InvalidItemException');
$this->expectExceptionMessage('The id field is required.'); $this->expectExceptionMessage('The id field is required.');
$this->cart->add('', 'Sample Item', 100.99, 2, []); $this->cart->add('', 'Sample Item', 100.99, 2, []);
}); });
test('should throw an exception when no name is provided', function () { test('should throw an exception when no name is provided', function () {
$this->expectException('Wearepixel\Cart\Exceptions\InvalidItemException'); $this->expectException('Joelwmale\Cart\Exceptions\InvalidItemException');
$this->expectExceptionMessage('The name field is required.'); $this->expectExceptionMessage('The name field is required.');
$this->cart->add(523, '', 100.99, 2, []); $this->cart->add(523, '', 100.99, 2, []);
}); });
@@ -646,7 +646,7 @@
expect($this->cart->getContent()->count())->toEqual(1, 'Cart should have 1 item on it'); expect($this->cart->getContent()->count())->toEqual(1, 'Cart should have 1 item on it');
expect($this->cart->getContent()->first()['id'])->toEqual(456, 'The first content must have ID of 456'); expect($this->cart->getContent()->first()['id'])->toEqual(456, 'The first content must have ID of 456');
expect($this->cart->getContent()->first()['name'])->toEqual('Sample Item', 'The first content must have name of "Sample Item"'); expect($this->cart->getContent()->first()['name'])->toEqual('Sample Item', 'The first content must have name of "Sample Item"');
expect($addedItem->model)->toBeInstanceOf('Wearepixel\Cart\Tests\Helpers\MockProduct'); expect($addedItem->model)->toBeInstanceOf('Joelwmale\Cart\Tests\Helpers\MockProduct');
}); });
test('cart can add items with multidimensional array with associated model', function () { test('cart can add items with multidimensional array with associated model', function () {
@@ -681,7 +681,7 @@
$content = $this->cart->getContent(); $content = $this->cart->getContent();
foreach ($content as $item) { foreach ($content as $item) {
expect($item->model)->toBeInstanceOf('Wearepixel\Cart\Tests\Helpers\MockProduct'); expect($item->model)->toBeInstanceOf('Joelwmale\Cart\Tests\Helpers\MockProduct');
} }
expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty'); expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty');
+23 -23
View File
@@ -1,7 +1,7 @@
<?php <?php
use Wearepixel\Cart\Cart; use Joelwmale\Cart\Cart;
use Wearepixel\Cart\Tests\Helpers\SessionMock; use Joelwmale\Cart\Tests\Helpers\SessionMock;
beforeEach(function () {}); beforeEach(function () {});
@@ -9,9 +9,9 @@
Mockery::close(); Mockery::close();
}); });
test('event cart created', function () { test('event cart Created', function () {
$events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher'); $events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher');
$events->shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); $events->shouldReceive('dispatch')->once()->with('LaravelCart.Created', Mockery::type('array'), true);
$cart = new Cart( $cart = new Cart(
new SessionMock, new SessionMock,
@@ -26,9 +26,9 @@
test('event cart adding', function () { test('event cart adding', function () {
$events = Mockery::mock('Illuminate\Events\Dispatcher'); $events = Mockery::mock('Illuminate\Events\Dispatcher');
$events->shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); $events->shouldReceive('dispatch')->once()->with('LaravelCart.Created', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->once()->with('cart.adding', Mockery::type('array'), true); $events->shouldReceive('dispatch')->once()->with('LaravelCart.Adding', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->once()->with('cart.added', Mockery::type('array'), true); $events->shouldReceive('dispatch')->once()->with('LaravelCart.Added', Mockery::type('array'), true);
$cart = new Cart( $cart = new Cart(
new SessionMock, new SessionMock,
@@ -45,9 +45,9 @@
test('event cart adding multiple times', function () { test('event cart adding multiple times', function () {
$events = Mockery::mock('Illuminate\Events\Dispatcher'); $events = Mockery::mock('Illuminate\Events\Dispatcher');
$events->shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); $events->shouldReceive('dispatch')->once()->with('LaravelCart.Created', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->times(2)->with('cart.adding', Mockery::type('array'), true); $events->shouldReceive('dispatch')->times(2)->with('LaravelCart.Adding', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->times(2)->with('cart.added', Mockery::type('array'), true); $events->shouldReceive('dispatch')->times(2)->with('LaravelCart.Added', Mockery::type('array'), true);
$cart = new Cart( $cart = new Cart(
new SessionMock, new SessionMock,
@@ -65,9 +65,9 @@
test('event cart adding multiple times scenario two', function () { test('event cart adding multiple times scenario two', function () {
$events = Mockery::mock('Illuminate\Events\Dispatcher'); $events = Mockery::mock('Illuminate\Events\Dispatcher');
$events->shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); $events->shouldReceive('dispatch')->once()->with('LaravelCart.Created', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->times(3)->with('cart.adding', Mockery::type('array'), true); $events->shouldReceive('dispatch')->times(3)->with('LaravelCart.Adding', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->times(3)->with('cart.added', Mockery::type('array'), true); $events->shouldReceive('dispatch')->times(3)->with('LaravelCart.Added', Mockery::type('array'), true);
$items = [ $items = [
[ [
@@ -108,11 +108,11 @@
test('event cart remove item', function () { test('event cart remove item', function () {
$events = Mockery::mock('Illuminate\Events\Dispatcher'); $events = Mockery::mock('Illuminate\Events\Dispatcher');
$events->shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); $events->shouldReceive('dispatch')->once()->with('LaravelCart.Created', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->times(3)->with('cart.adding', Mockery::type('array'), true); $events->shouldReceive('dispatch')->times(3)->with('LaravelCart.Adding', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->times(3)->with('cart.added', Mockery::type('array'), true); $events->shouldReceive('dispatch')->times(3)->with('LaravelCart.Added', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->times(1)->with('cart.removing', Mockery::type('array'), true); $events->shouldReceive('dispatch')->times(1)->with('LaravelCart.Removing', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->times(1)->with('cart.removed', Mockery::type('array'), true); $events->shouldReceive('dispatch')->times(1)->with('LaravelCart.Removed', Mockery::type('array'), true);
$items = [ $items = [
[ [
@@ -155,11 +155,11 @@
test('event cart clear', function () { test('event cart clear', function () {
$events = Mockery::mock('Illuminate\Events\Dispatcher'); $events = Mockery::mock('Illuminate\Events\Dispatcher');
$events->shouldReceive('dispatch')->once()->with('cart.created', Mockery::type('array'), true); $events->shouldReceive('dispatch')->once()->with('LaravelCart.Created', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->times(3)->with('cart.adding', Mockery::type('array'), true); $events->shouldReceive('dispatch')->times(3)->with('LaravelCart.Adding', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->times(3)->with('cart.added', Mockery::type('array'), true); $events->shouldReceive('dispatch')->times(3)->with('LaravelCart.Added', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->once()->with('cart.clearing', Mockery::type('array'), true); $events->shouldReceive('dispatch')->once()->with('LaravelCart.Clearing', Mockery::type('array'), true);
$events->shouldReceive('dispatch')->once()->with('cart.cleared', Mockery::type('array'), true); $events->shouldReceive('dispatch')->once()->with('LaravelCart.Cleared', Mockery::type('array'), true);
$items = [ $items = [
[ [
+2 -2
View File
@@ -1,7 +1,7 @@
<?php <?php
use Wearepixel\Cart\Cart; use Joelwmale\Cart\Cart;
use Wearepixel\Cart\Tests\Helpers\SessionMock; use Joelwmale\Cart\Tests\Helpers\SessionMock;
beforeEach(function () { beforeEach(function () {
$events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher'); $events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher');
+126
View File
@@ -0,0 +1,126 @@
<?php
use Joelwmale\Cart\Cart;
use Joelwmale\Cart\CartCondition;
use Joelwmale\Cart\Tests\Helpers\MockCartModel;
beforeEach(function () {
$events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher');
$events->shouldReceive('dispatch');
$storage = new MockCartModel;
$this->cart = new Cart(
$storage,
$events,
'cart',
'SAMPLESESSIONKEY',
require (__DIR__ . '/../Helpers/ConfigDatabaseMock.php')
);
});
afterEach(function () {
Mockery::close();
});
describe('database cart', function () {
test('can add an item', function () {
$this->cart->add(1, 'Sample Item', 100.99, 2, []);
expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty');
expect($this->cart->getContent()->count())->toEqual(1, 'Cart content should be 1');
expect($this->cart->getContent()->first()['id'])->toEqual(1, 'Item added has ID of 1 so first content ID should be 1');
expect($this->cart->getContent()->first()['price'])->toEqual(100.99, 'Item added has price of 100.99 so first content price should be 100.99');
expect(MockCartModel::all()->count())->toEqual(1, 'The carts database table should have 1 row');
});
test('can add and remove an item', function () {
$this->cart->add(1, 'Sample Item', 100.99, 2, []);
expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty');
expect($this->cart->getContent()->count())->toEqual(1, 'Cart content should be 1');
expect($this->cart->getContent()->first()['id'])->toEqual(1, 'Item added has ID of 1 so first content ID should be 1');
expect($this->cart->getContent()->first()['price'])->toEqual(100.99, 'Item added has price of 100.99 so first content price should be 100.99');
expect(MockCartModel::all()->count())->toEqual(1, 'The carts database table should have 1 row');
$this->cart->remove(1);
$cartModel = MockCartModel::first();
expect($this->cart->isEmpty())->toBeTrue('Cart should be empty');
expect($this->cart->getContent()->count())->toEqual(0, 'Cart content should be 0');
expect($cartModel->items)->toEqual([], 'The carts database table should have 0 row');
});
test('can add an item with an item level condition', function () {
$saleCondition = new CartCondition([
'name' => '5% Discount',
'type' => 'sale',
'value' => '-5%',
]);
$item = [
'id' => 1,
'name' => 'Chicken Curry',
'price' => 18.95,
'quantity' => 1,
'attributes' => [],
'conditions' => $saleCondition,
];
$this->cart->add($item);
expect($this->cart->get(1)->getPriceSum())->toEqual(18.95);
expect($this->cart->get(1)->getPriceSumWithConditions())->toEqual(18);
expect($this->cart->getSubTotal())->toEqual(18, 'Cart should have subtotal of 18');
expect($this->cart->getTotal())->toEqual(18, 'Cart should have total of 18');
});
test('can add an item with multiple item level conditions', function () {
$saleCondition = new CartCondition([
'name' => '5% Discount',
'type' => 'sale',
'value' => '-5%',
]);
$gstCondition = new CartCondition([
'name' => 'GST',
'type' => 'gst',
'value' => '2.5%',
]);
$item = [
'id' => 1,
'name' => 'Chicken Curry',
'price' => 18.95,
'quantity' => 1,
'attributes' => [],
'conditions' => [$saleCondition, $gstCondition],
];
$this->cart->add($item);
expect($this->cart->get(1)->getPriceSum())->toEqual(18.95);
expect($this->cart->get(1)->getPriceSumWithConditions())->toEqual(18.45);
expect($this->cart->getSubTotal())->toEqual(18.45, 'Cart should have subtotal of 18.45');
expect($this->cart->getTotal())->toEqual(18.45, 'Cart should have total of 18.45');
});
test('can clear a simple cart', function () {
$this->cart->add(1, 'Sample Item', 100.99, 2, []);
expect($this->cart->isEmpty())->toBeFalse('Cart should not be empty');
expect($this->cart->getContent()->count())->toEqual(1, 'Cart content should be 1');
expect($this->cart->getContent()->first()['id'])->toEqual(1, 'Item added has ID of 1 so first content ID should be 1');
expect($this->cart->getContent()->first()['price'])->toEqual(100.99, 'Item added has price of 100.99 so first content price should be 100.99');
expect(MockCartModel::all()->count())->toEqual(1, 'The carts database table should have 1 row');
$this->cart->clear();
expect($this->cart->isEmpty())->toBeTrue('Cart should be empty');
expect($this->cart->getContent()->count())->toEqual(0, 'Cart content should be 0');
expect(MockCartModel::all()->count())->toEqual(0, 'The carts database table should have 0 rows');
});
});
+6 -6
View File
@@ -1,9 +1,9 @@
<?php <?php
use Wearepixel\Cart\Cart; use Joelwmale\Cart\Cart;
use Wearepixel\Cart\CartCondition; use Joelwmale\Cart\CartCondition;
use Wearepixel\Cart\Tests\Helpers\MockProduct; use Joelwmale\Cart\Tests\Helpers\MockProduct;
use Wearepixel\Cart\Tests\Helpers\SessionMock; use Joelwmale\Cart\Tests\Helpers\SessionMock;
beforeEach(function () { beforeEach(function () {
$events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher'); $events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher');
@@ -47,7 +47,7 @@
}); });
test('item get conditions with conditions', function () { test('item get conditions with conditions', function () {
$itemCondition1 = new \Wearepixel\Cart\CartCondition([ $itemCondition1 = new \Joelwmale\Cart\CartCondition([
'name' => 'SALE 5%', 'name' => 'SALE 5%',
'type' => 'sale', 'type' => 'sale',
'target' => 'item', 'target' => 'item',
@@ -77,7 +77,7 @@
}); });
test('it will throw an exception when a non existing model is being associated', function () { test('it will throw an exception when a non existing model is being associated', function () {
$this->expectException(\Wearepixel\Cart\Exceptions\UnknownModelException::class); $this->expectException(\Joelwmale\Cart\Exceptions\UnknownModelException::class);
$this->expectExceptionMessage('The supplied model SomeModel does not exist.'); $this->expectExceptionMessage('The supplied model SomeModel does not exist.');
$this->cart->add(1, 'Test item', 1, 10.00)->associate('SomeModel'); $this->cart->add(1, 'Test item', 1, 10.00)->associate('SomeModel');
+2 -2
View File
@@ -1,7 +1,7 @@
<?php <?php
use Wearepixel\Cart\Cart; use Joelwmale\Cart\Cart;
use Wearepixel\Cart\Tests\Helpers\SessionMock; use Joelwmale\Cart\Tests\Helpers\SessionMock;
beforeEach(function () { beforeEach(function () {
$events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher'); $events = Mockery::mock('Illuminate\Contracts\Events\Dispatcher');