mirror of
https://github.com/kevin-DL/LaravelShoppingcart.git
synced 2026-01-22 15:15:27 +00:00
Merge pull request #146 from bumbummen99/refactor-moneyphp
Add MoneyPHP
This commit is contained in:
29
.github/workflows/php.yml
vendored
29
.github/workflows/php.yml
vendored
@@ -22,34 +22,27 @@ jobs:
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-versions }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: bcmath,gmp,intl
|
||||
env:
|
||||
runner: ubuntu-18.04
|
||||
|
||||
- name: Use appropiate Laravel version
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Use appropiate Laravel version in composer.json (without installing)
|
||||
run: |
|
||||
composer require "laravel/framework:${{ matrix.laravel-version }}" --no-interaction --no-update
|
||||
composer require --no-update --no-interaction "laravel/framework:${{ matrix.laravel-version }}"
|
||||
composer validate
|
||||
|
||||
- name: Validate composer.json and composer.lock
|
||||
run: composer validate
|
||||
|
||||
- name: Get composer cache directory
|
||||
id: composercache
|
||||
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
|
||||
|
||||
- name: Cache Composer packages
|
||||
id: composer-cache
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ${{ steps.composercache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-composer-
|
||||
path: ~/.composer/cache/files
|
||||
key: dependencies-laravel-${{ matrix.laravel-version }}-php-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }}
|
||||
|
||||
- name: Install composer packages
|
||||
run: composer install --prefer-dist --no-interaction
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,4 +3,5 @@ composer.phar
|
||||
composer.lock
|
||||
.DS_Store
|
||||
coverage.xml
|
||||
.phpunit.result.cache
|
||||
.phpunit.result.cache
|
||||
/.vscode
|
||||
@@ -19,8 +19,6 @@ Run the Composer require command from the Terminal:
|
||||
|
||||
Now you're ready to start using the shoppingcart in your application.
|
||||
|
||||
**As of version 2 of this package it's possibly to use dependency injection to inject an instance of the Cart class into your controller or other class**
|
||||
|
||||
You definitely should publish the `config` file and take a look at it.
|
||||
|
||||
php artisan vendor:publish --provider="Gloudemans\Shoppingcart\ShoppingcartServiceProvider" --tag="config"
|
||||
@@ -611,7 +609,7 @@ class DefaultCalculator implements Calculator
|
||||
{
|
||||
public static function getAttribute(string $attribute, CartItem $cartItem)
|
||||
{
|
||||
$decimals = config('cart.format.decimals', 2);
|
||||
$decimals = Config::get('cart.format.decimals', 2);
|
||||
|
||||
switch ($attribute) {
|
||||
case 'discount':
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
"illuminate/support": "^6.0|^7.0|^8.0|^9.0",
|
||||
"illuminate/session": "^6.0|^7.0|^8.0|^9.0",
|
||||
"illuminate/events": "^6.0|^7.0|^8.0|^9.0",
|
||||
"nesbot/carbon": "^2.0"
|
||||
"illuminate/database": "^6.0|^7.0|^8.0|^9.0",
|
||||
"nesbot/carbon": "^2.0",
|
||||
"moneyphp/money": "^4.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~8.0|~9.0",
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Gloudemans\Shoppingcart\Calculation;
|
||||
|
||||
use Gloudemans\Shoppingcart\CartItem;
|
||||
use Gloudemans\Shoppingcart\Contracts\Calculator;
|
||||
|
||||
class DefaultCalculator implements Calculator
|
||||
{
|
||||
public static function getAttribute(string $attribute, CartItem $cartItem)
|
||||
{
|
||||
$decimals = config('cart.format.decimals', 2);
|
||||
|
||||
switch ($attribute) {
|
||||
case 'discount':
|
||||
return $cartItem->price * ($cartItem->getDiscountRate() / 100);
|
||||
case 'tax':
|
||||
return round($cartItem->priceTarget * ($cartItem->taxRate / 100), $decimals);
|
||||
case 'priceTax':
|
||||
return round($cartItem->priceTarget + $cartItem->tax, $decimals);
|
||||
case 'discountTotal':
|
||||
return round($cartItem->discount * $cartItem->qty, $decimals);
|
||||
case 'priceTotal':
|
||||
return round($cartItem->price * $cartItem->qty, $decimals);
|
||||
case 'subtotal':
|
||||
return max(round($cartItem->priceTotal - $cartItem->discountTotal, $decimals), 0);
|
||||
case 'priceTarget':
|
||||
return round(($cartItem->priceTotal - $cartItem->discountTotal) / $cartItem->qty, $decimals);
|
||||
case 'taxTotal':
|
||||
return round($cartItem->subtotal * ($cartItem->taxRate / 100), $decimals);
|
||||
case 'total':
|
||||
return round($cartItem->subtotal + $cartItem->taxTotal, $decimals);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Gloudemans\Shoppingcart\Calculation;
|
||||
|
||||
use Gloudemans\Shoppingcart\CartItem;
|
||||
use Gloudemans\Shoppingcart\Contracts\Calculator;
|
||||
|
||||
class GrossPrice implements Calculator
|
||||
{
|
||||
public static function getAttribute(string $attribute, CartItem $cartItem)
|
||||
{
|
||||
$decimals = config('cart.format.decimals', 2);
|
||||
|
||||
switch ($attribute) {
|
||||
case 'priceNet':
|
||||
return round($cartItem->price / (1 + ($cartItem->taxRate / 100)), $decimals);
|
||||
case 'discount':
|
||||
return $cartItem->priceNet * ($cartItem->getDiscountRate() / 100);
|
||||
case 'tax':
|
||||
return round($cartItem->priceTarget * ($cartItem->taxRate / 100), $decimals);
|
||||
case 'priceTax':
|
||||
return round($cartItem->priceTarget + $cartItem->tax, $decimals);
|
||||
case 'discountTotal':
|
||||
return round($cartItem->discount * $cartItem->qty, $decimals);
|
||||
case 'priceTotal':
|
||||
return round($cartItem->priceNet * $cartItem->qty, $decimals);
|
||||
case 'subtotal':
|
||||
return max(round($cartItem->priceTotal - $cartItem->discountTotal, $decimals), 0);
|
||||
case 'priceTarget':
|
||||
return round(($cartItem->priceTotal - $cartItem->discountTotal) / $cartItem->qty, $decimals);
|
||||
case 'taxTotal':
|
||||
return round($cartItem->subtotal * ($cartItem->taxRate / 100), $decimals);
|
||||
case 'total':
|
||||
return round($cartItem->subtotal + $cartItem->taxTotal, $decimals);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace Gloudemans\Shoppingcart;
|
||||
|
||||
use Money\Currency;
|
||||
use Money\Money;
|
||||
|
||||
trait CanBeBought
|
||||
{
|
||||
/**
|
||||
@@ -9,47 +12,41 @@ trait CanBeBought
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
public function getBuyableIdentifier()
|
||||
public function getBuyableIdentifier(CartItemOptions $options)
|
||||
{
|
||||
return method_exists($this, 'getKey') ? $this->getKey() : $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name, title or description of the Buyable item.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBuyableDescription() : ?string
|
||||
public function getBuyableDescription(CartItemOptions $options): ?string
|
||||
{
|
||||
if (($name = $this->getAttribute('name'))) {
|
||||
return $name;
|
||||
} else if (($title = $this->getAttribute('title'))) {
|
||||
} elseif (($title = $this->getAttribute('title'))) {
|
||||
return $title;
|
||||
} else if (($description = $this->getAttribute('description'))) {
|
||||
} elseif (($description = $this->getAttribute('description'))) {
|
||||
return $description;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price of the Buyable item.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getBuyablePrice()
|
||||
public function getBuyablePrice(CartItemOptions $options): Money
|
||||
{
|
||||
if (($price = $this->getAttribute('price'))) {
|
||||
return $price;
|
||||
if (($price = $this->getAttribute('price')) && ($currency = $this->getAttribute('currency'))) {
|
||||
return new Money($price, new Currency($currency));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weight of the Buyable item.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getBuyableWeight()
|
||||
public function getBuyableWeight(CartItemOptions $options): int
|
||||
{
|
||||
if (($weight = $this->getAttribute('weight'))) {
|
||||
return $weight;
|
||||
|
||||
431
src/Cart.php
431
src/Cart.php
@@ -11,9 +11,14 @@ use Gloudemans\Shoppingcart\Exceptions\InvalidRowIDException;
|
||||
use Gloudemans\Shoppingcart\Exceptions\UnknownModelException;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Illuminate\Database\DatabaseManager;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Session\SessionManager;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Traits\Macroable;
|
||||
use InvalidArgumentException;
|
||||
use Money\Currency;
|
||||
use Money\Money;
|
||||
|
||||
class Cart
|
||||
{
|
||||
@@ -33,31 +38,23 @@ class Cart
|
||||
|
||||
/**
|
||||
* Holds the current cart instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $instance;
|
||||
|
||||
/**
|
||||
* Holds the creation date of the cart.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $createdAt;
|
||||
private ?Carbon $createdAt = null;
|
||||
|
||||
/**
|
||||
* Holds the update date of the cart.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $updatedAt;
|
||||
private ?Carbon $updatedAt = null;
|
||||
|
||||
/**
|
||||
* Defines the discount percentage.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
private $discount = 0;
|
||||
private float $discount = 0;
|
||||
|
||||
/**
|
||||
* Defines the tax rate.
|
||||
@@ -76,7 +73,7 @@ class Cart
|
||||
{
|
||||
$this->session = $session;
|
||||
$this->events = $events;
|
||||
$this->taxRate = config('cart.tax');
|
||||
$this->taxRate = Config::get('cart.tax');
|
||||
|
||||
$this->instance(self::DEFAULT_INSTANCE);
|
||||
}
|
||||
@@ -88,7 +85,7 @@ class Cart
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\Cart
|
||||
*/
|
||||
public function instance($instance = null)
|
||||
public function instance($instance = null): self
|
||||
{
|
||||
$instance = $instance ?: self::DEFAULT_INSTANCE;
|
||||
|
||||
@@ -107,34 +104,65 @@ class Cart
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function currentInstance()
|
||||
public function currentInstance(): string
|
||||
{
|
||||
return str_replace('cart.', '', $this->instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the cart.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param mixed $name
|
||||
* @param int|float $qty
|
||||
* @param float $price
|
||||
* @param float $weight
|
||||
* @param array $options
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public function add($id, ?string $name = null, $qty = null, $price = null, $weight = 0, array $options = [])
|
||||
public function add(int|string|Buyable|array $id, null|string|int $nameOrQty = null, null|int|CartItemOptions $qtyOrOptions = null, ?Money $price = null, ?int $weight = null, ?CartItemOptions $options = null): CartItem|array
|
||||
{
|
||||
if ($this->isMulti($id)) {
|
||||
return array_map(function ($item) {
|
||||
return $this->add($item);
|
||||
}, $id);
|
||||
/* Allow adding a CartItem by raw parameters */
|
||||
if (is_int($id) || is_string($id)) {
|
||||
if (!is_null($nameOrQty) && !is_string($nameOrQty)) {
|
||||
throw new InvalidArgumentException('$nameOrQty must be of type string (name) or null when adding with raw parameters');
|
||||
}
|
||||
|
||||
if (!is_null($qtyOrOptions) && !is_int($qtyOrOptions)) {
|
||||
throw new InvalidArgumentException('$nameOrQty must be of type int (quantity) or null when adding with raw parameters');
|
||||
}
|
||||
|
||||
return $this->addCartItem(CartItem::fromAttributes($id, $nameOrQty, $price, $qtyOrOptions ?: 1, $weight ?: 0, $options ?: new CartItemOptions([])));
|
||||
}
|
||||
/* Also allow passing a Buyable instance, get data from the instance rather than parameters */
|
||||
elseif ($id instanceof Buyable) {
|
||||
if (!is_null($qtyOrOptions) && !is_int($nameOrQty)) {
|
||||
throw new InvalidArgumentException('$nameOrQty must be of type int (quantity) when adding a Buyable instance');
|
||||
}
|
||||
|
||||
$cartItem = $this->createCartItem($id, $name, $qty, $price, $weight, $options);
|
||||
if (!is_null($qtyOrOptions) && !$qtyOrOptions instanceof CartItemOptions) {
|
||||
throw new InvalidArgumentException('$qtyOrOptions must be of type CartItemOptions (options) or null when adding a Buyable instance');
|
||||
}
|
||||
|
||||
return $this->addCartItem($cartItem);
|
||||
$cartItem = CartItem::fromBuyable($id, $nameOrQty ?: 1, $qtyOrOptions ?: new CartItemOptions([]));
|
||||
|
||||
if ($id instanceof Model) {
|
||||
$cartItem->associate($id);
|
||||
}
|
||||
|
||||
return $this->addCartItem($cartItem);
|
||||
}
|
||||
/* Also allow passing multiple definitions at the same time, simply call same method and collec return value */
|
||||
elseif (is_array($id)) {
|
||||
/* Check if this iterable contains instances */
|
||||
if (is_array(head($id)) || head($id) instanceof Buyable) {
|
||||
return array_map(function (Buyable|iterable $item) {
|
||||
return $this->add($item);
|
||||
}, $id);
|
||||
}
|
||||
/* Treat the array itself as an instance */
|
||||
else {
|
||||
$cartItem = CartItem::fromArray($id);
|
||||
|
||||
return $this->addCartItem($cartItem);
|
||||
}
|
||||
}
|
||||
/* Due to PHP8 union types this should never happen */
|
||||
else {
|
||||
throw new InvalidArgumentException('$id must be of type int|string|Buyable|Iterable');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,10 +175,12 @@ class Cart
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem The CartItem
|
||||
*/
|
||||
public function addCartItem(CartItem $item, bool $keepDiscount = false, bool $keepTax = false, bool $dispatchEvent = true)
|
||||
public function addCartItem(CartItem $item, bool $keepDiscount = false, bool $keepTax = false, bool $dispatchEvent = true): CartItem
|
||||
{
|
||||
$item->setInstance($this->currentInstance());
|
||||
|
||||
if (!$keepDiscount) {
|
||||
$item->setDiscountRate($this->discount);
|
||||
$item->setDiscount($this->discount);
|
||||
}
|
||||
|
||||
if (!$keepTax) {
|
||||
@@ -186,7 +216,7 @@ class Cart
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public function update(string $rowId, $qty)
|
||||
public function update(string $rowId, $qty): ?CartItem
|
||||
{
|
||||
$cartItem = $this->get($rowId);
|
||||
|
||||
@@ -214,7 +244,7 @@ class Cart
|
||||
if ($cartItem->qty <= 0) {
|
||||
$this->remove($cartItem->rowId);
|
||||
|
||||
return;
|
||||
return null;
|
||||
} else {
|
||||
if (isset($itemOldIndex)) {
|
||||
$content = $content->slice(0, $itemOldIndex)
|
||||
@@ -241,7 +271,7 @@ class Cart
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function remove(string $rowId)
|
||||
public function remove(string $rowId): void
|
||||
{
|
||||
$cartItem = $this->get($rowId);
|
||||
|
||||
@@ -263,7 +293,7 @@ class Cart
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public function get(string $rowId)
|
||||
public function get(string $rowId): CartItem
|
||||
{
|
||||
$content = $this->getContent();
|
||||
|
||||
@@ -271,7 +301,11 @@ class Cart
|
||||
throw new InvalidRowIDException("The cart does not contain rowId {$rowId}.");
|
||||
}
|
||||
|
||||
return $content->get($rowId);
|
||||
$cartItem = $content->get($rowId);
|
||||
|
||||
if ($cartItem instanceof CartItem) {
|
||||
return $cartItem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,7 +313,7 @@ class Cart
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function destroy()
|
||||
public function destroy(): void
|
||||
{
|
||||
$this->session->remove($this->instance);
|
||||
}
|
||||
@@ -289,7 +323,7 @@ class Cart
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function content()
|
||||
public function content(): Collection
|
||||
{
|
||||
if (is_null($this->session->get($this->instance))) {
|
||||
return new Collection([]);
|
||||
@@ -300,10 +334,8 @@ class Cart
|
||||
|
||||
/**
|
||||
* Get the total quantity of all CartItems in the cart.
|
||||
*
|
||||
* @return int|float
|
||||
*/
|
||||
public function count()
|
||||
public function count(): int
|
||||
{
|
||||
return $this->getContent()->sum('qty');
|
||||
}
|
||||
@@ -312,69 +344,27 @@ class Cart
|
||||
* Get the amount of CartItems in the Cart.
|
||||
* Keep in mind that this does NOT count quantity.
|
||||
*/
|
||||
public function countItems() : int
|
||||
public function countItems(): int
|
||||
{
|
||||
return $this->getContent()->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total price of the items in the cart.
|
||||
*/
|
||||
public function totalFloat() : float
|
||||
{
|
||||
return $this->getContent()->reduce(function ($total, CartItem $cartItem) {
|
||||
return $total + $cartItem->total;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total price of the items in the cart as formatted string.
|
||||
*/
|
||||
public function total(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
|
||||
{
|
||||
return $this->numberFormat($this->totalFloat(), $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total tax of the items in the cart.
|
||||
*/
|
||||
public function taxFloat() : float
|
||||
{
|
||||
return $this->getContent()->reduce(function ($tax, CartItem $cartItem) {
|
||||
return $tax + $cartItem->taxTotal;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total tax of the items in the cart as formatted string.
|
||||
*/
|
||||
public function tax(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
|
||||
{
|
||||
return $this->numberFormat($this->taxFloat(), $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subtotal (total - tax) of the items in the cart.
|
||||
*/
|
||||
public function subtotalFloat() : float
|
||||
{
|
||||
return $this->getContent()->reduce(function ($subTotal, CartItem $cartItem) {
|
||||
return $subTotal + $cartItem->subtotal;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subtotal (total - tax) of the items in the cart as formatted string.
|
||||
* Get the discount of the items in the cart.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
* @return Money
|
||||
*/
|
||||
public function subtotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
|
||||
public function price(): Money
|
||||
{
|
||||
return $this->numberFormat($this->subtotalFloat(), $decimals, $decimalPoint, $thousandSeperator);
|
||||
$calculated = $this->getContent()->reduce(function (Money $discount, CartItem $cartItem) {
|
||||
return $discount->add($cartItem->price());
|
||||
}, new Money(0, new Currency('USD')));
|
||||
|
||||
if ($calculated instanceof Money) {
|
||||
return $calculated;
|
||||
} else {
|
||||
throw new \TypeError('Calculated price is not an instance of Money');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,79 +372,79 @@ class Cart
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function discountFloat() : float
|
||||
public function discount(): Money
|
||||
{
|
||||
return $this->getContent()->reduce(function ($discount, CartItem $cartItem) {
|
||||
return $discount + $cartItem->discountTotal;
|
||||
}, 0);
|
||||
$calculated = $this->getContent()->reduce(function (Money $discount, CartItem $cartItem) {
|
||||
return $discount->add($cartItem->discount());
|
||||
}, new Money(0, new Currency('USD')));
|
||||
|
||||
if ($calculated instanceof Money) {
|
||||
return $calculated;
|
||||
} else {
|
||||
throw new \TypeError('Calculated discount is not an instance of Money');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the discount of the items in the cart as formatted string.
|
||||
* Get the subtotal (total - tax) of the items in the cart.
|
||||
*/
|
||||
public function discount(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
|
||||
public function subtotal(): Money
|
||||
{
|
||||
return $this->numberFormat($this->discountFloat(), $decimals, $decimalPoint, $thousandSeperator);
|
||||
$calculated = $this->getContent()->reduce(function (Money $subTotal, CartItem $cartItem) {
|
||||
return $subTotal->add($cartItem->subtotal());
|
||||
}, new Money(0, new Currency('USD')));
|
||||
|
||||
if ($calculated instanceof Money) {
|
||||
return $calculated;
|
||||
} else {
|
||||
throw new \TypeError('Calculated subtotal is not an instance of Money');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price of the items in the cart (not rounded).
|
||||
* Get the total tax of the items in the cart.
|
||||
*/
|
||||
public function initialFloat() : float
|
||||
public function tax(): Money
|
||||
{
|
||||
return $this->getContent()->reduce(function ($initial, CartItem $cartItem) {
|
||||
return $initial + ($cartItem->qty * $cartItem->price);
|
||||
}, 0);
|
||||
$calculated = $this->getContent()->reduce(function (Money $tax, CartItem $cartItem) {
|
||||
return $tax->add($cartItem->tax());
|
||||
}, new Money(0, new Currency('USD')));
|
||||
|
||||
if ($calculated instanceof Money) {
|
||||
return $calculated;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price of the items in the cart as formatted string.
|
||||
* Get the total price of the items in the cart.
|
||||
*/
|
||||
public function initial(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
|
||||
public function total(): Money
|
||||
{
|
||||
return $this->numberFormat($this->initialFloat(), $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
$calculated = $this->getContent()->reduce(function (Money $total, CartItem $cartItem) {
|
||||
return $total->add($cartItem->total());
|
||||
}, new Money(0, new Currency('USD')));
|
||||
|
||||
/**
|
||||
* Get the price of the items in the cart (previously rounded).
|
||||
*/
|
||||
public function priceTotalFloat() : float
|
||||
{
|
||||
return $this->getContent()->reduce(function ($initial, CartItem $cartItem) {
|
||||
return $initial + $cartItem->priceTotal;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price of the items in the cart as formatted string.
|
||||
*/
|
||||
public function priceTotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
|
||||
{
|
||||
return $this->numberFormat($this->priceTotalFloat(), $decimals, $decimalPoint, $thousandSeperator);
|
||||
if ($calculated instanceof Money) {
|
||||
return $calculated;
|
||||
} else {
|
||||
throw new \TypeError('Calculated total is not an instance of Money');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total weight of the items in the cart.
|
||||
*/
|
||||
public function weightFloat() : float
|
||||
public function weight(): int
|
||||
{
|
||||
return $this->getContent()->reduce(function ($total, CartItem $cartItem) {
|
||||
return $total + ($cartItem->qty * $cartItem->weight);
|
||||
$calculated = $this->getContent()->reduce(function (int $total, CartItem $cartItem) {
|
||||
return $total + $cartItem->weight();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total weight of the items in the cart.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function weight(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null) : string
|
||||
{
|
||||
return $this->numberFormat($this->weightFloat(), $decimals, $decimalPoint, $thousandSeperator);
|
||||
if (is_int($calculated)) {
|
||||
return $calculated;
|
||||
} else {
|
||||
throw new \TypeError('Calculated weight was not an integer');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -464,7 +454,7 @@ class Cart
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function search(Closure $search) : Collection
|
||||
public function search(Closure $search): Collection
|
||||
{
|
||||
return $this->getContent()->filter($search);
|
||||
}
|
||||
@@ -477,7 +467,7 @@ class Cart
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function associate(string $rowId, $model)
|
||||
public function associate(string $rowId, $model): void
|
||||
{
|
||||
if (is_string($model) && !class_exists($model)) {
|
||||
throw new UnknownModelException("The supplied model {$model} does not exist.");
|
||||
@@ -502,7 +492,7 @@ class Cart
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setTax(string $rowId, $taxRate)
|
||||
public function setTax(string $rowId, $taxRate): void
|
||||
{
|
||||
$cartItem = $this->get($rowId);
|
||||
|
||||
@@ -521,7 +511,7 @@ class Cart
|
||||
*
|
||||
* @param float $discount
|
||||
*/
|
||||
public function setGlobalTax($taxRate)
|
||||
public function setGlobalTax($taxRate): void
|
||||
{
|
||||
$this->taxRate = $taxRate;
|
||||
|
||||
@@ -535,17 +525,12 @@ class Cart
|
||||
|
||||
/**
|
||||
* Set the discount rate for the cart item with the given rowId.
|
||||
*
|
||||
* @param string $rowId
|
||||
* @param int|float $taxRate
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDiscount(string $rowId, $discount)
|
||||
public function setDiscount(string $rowId, float|Money $discount): void
|
||||
{
|
||||
$cartItem = $this->get($rowId);
|
||||
|
||||
$cartItem->setDiscountRate($discount);
|
||||
$cartItem->setDiscount($discount);
|
||||
|
||||
$content = $this->getContent();
|
||||
|
||||
@@ -562,14 +547,14 @@ class Cart
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setGlobalDiscount($discount)
|
||||
public function setGlobalDiscount(float $discount): void
|
||||
{
|
||||
$this->discount = $discount;
|
||||
|
||||
$content = $this->getContent();
|
||||
if ($content && $content->count()) {
|
||||
$content->each(function ($item, $key) {
|
||||
$item->setDiscountRate($this->discount);
|
||||
$content->each(function (CartItem $item, $key) {
|
||||
$item->setDiscount($this->discount);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -581,7 +566,7 @@ class Cart
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function store($identifier)
|
||||
public function store($identifier): void
|
||||
{
|
||||
$content = $this->getContent();
|
||||
|
||||
@@ -595,7 +580,7 @@ class Cart
|
||||
throw new CartAlreadyStoredException("A cart with identifier {$identifier} was already stored.");
|
||||
}
|
||||
|
||||
$this->getConnection()->table($this->getTableName())->insert([
|
||||
$this->getConnection()->table(self::getTableName())->insert([
|
||||
'identifier' => $identifier,
|
||||
'instance' => $instance,
|
||||
'content' => serialize($content),
|
||||
@@ -613,7 +598,7 @@ class Cart
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function restore($identifier)
|
||||
public function restore($identifier): void
|
||||
{
|
||||
if ($identifier instanceof InstanceIdentifier) {
|
||||
$identifier = $identifier->getInstanceIdentifier();
|
||||
@@ -625,7 +610,7 @@ class Cart
|
||||
return;
|
||||
}
|
||||
|
||||
$stored = $this->getConnection()->table($this->getTableName())
|
||||
$stored = $this->getConnection()->table(self::getTableName())
|
||||
->where(['identifier'=> $identifier, 'instance' => $currentInstance])->first();
|
||||
|
||||
$storedContent = unserialize(data_get($stored, 'content'));
|
||||
@@ -647,7 +632,7 @@ class Cart
|
||||
$this->createdAt = Carbon::parse(data_get($stored, 'created_at'));
|
||||
$this->updatedAt = Carbon::parse(data_get($stored, 'updated_at'));
|
||||
|
||||
$this->getConnection()->table($this->getTableName())->where(['identifier' => $identifier, 'instance' => $currentInstance])->delete();
|
||||
$this->getConnection()->table(self::getTableName())->where(['identifier' => $identifier, 'instance' => $currentInstance])->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -657,7 +642,7 @@ class Cart
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function erase($identifier)
|
||||
public function erase($identifier): void
|
||||
{
|
||||
if ($identifier instanceof InstanceIdentifier) {
|
||||
$identifier = $identifier->getInstanceIdentifier();
|
||||
@@ -669,7 +654,7 @@ class Cart
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getConnection()->table($this->getTableName())->where(['identifier' => $identifier, 'instance' => $instance])->delete();
|
||||
$this->getConnection()->table(self::getTableName())->where(['identifier' => $identifier, 'instance' => $instance])->delete();
|
||||
|
||||
$this->events->dispatch('cart.erased');
|
||||
}
|
||||
@@ -684,13 +669,13 @@ class Cart
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function merge($identifier, bool $keepDiscount = false, bool $keepTax = false, bool $dispatchAdd = true, $instance = self::DEFAULT_INSTANCE)
|
||||
public function merge($identifier, bool $keepDiscount = false, bool $keepTax = false, bool $dispatchAdd = true, $instance = self::DEFAULT_INSTANCE): bool
|
||||
{
|
||||
if (!$this->storedCartInstanceWithIdentifierExists($instance, $identifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stored = $this->getConnection()->table($this->getTableName())
|
||||
$stored = $this->getConnection()->table(self::getTableName())
|
||||
->where(['identifier'=> $identifier, 'instance'=> $instance])->first();
|
||||
|
||||
$storedContent = unserialize($stored->content);
|
||||
@@ -708,10 +693,8 @@ class Cart
|
||||
* Magic method to make accessing the total, tax and subtotal properties possible.
|
||||
*
|
||||
* @param string $attribute
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function __get(string $attribute)
|
||||
public function __get(string $attribute): ?Money
|
||||
{
|
||||
switch ($attribute) {
|
||||
case 'total':
|
||||
@@ -721,16 +704,14 @@ class Cart
|
||||
case 'subtotal':
|
||||
return $this->subtotal();
|
||||
default:
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the carts content, if there is no cart content set yet, return a new empty Collection.
|
||||
*
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
protected function getContent() : Collection
|
||||
protected function getContent(): Collection
|
||||
{
|
||||
if ($this->session->has($this->instance)) {
|
||||
return $this->session->get($this->instance);
|
||||
@@ -739,69 +720,18 @@ class Cart
|
||||
return new Collection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new CartItem from the supplied attributes.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @param mixed $name
|
||||
* @param int|float $qty
|
||||
* @param float $price
|
||||
* @param float $weight
|
||||
* @param array $options
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
private function createCartItem($id, ?string $name = null, $qty, $price, $weight, array $options) : CartItem
|
||||
{
|
||||
if ($id instanceof Buyable) {
|
||||
$cartItem = CartItem::fromBuyable($id, $qty ?: []);
|
||||
$cartItem->setQuantity($name ?: 1);
|
||||
$cartItem->associate($id);
|
||||
} elseif (is_array($id)) {
|
||||
$cartItem = CartItem::fromArray($id);
|
||||
$cartItem->setQuantity($id['qty']);
|
||||
} else {
|
||||
$cartItem = CartItem::fromAttributes($id, $name, $price, $weight, $options);
|
||||
$cartItem->setQuantity($qty);
|
||||
}
|
||||
|
||||
$cartItem->setInstance($this->currentInstance());
|
||||
|
||||
return $cartItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the item is a multidimensional array or an array of Buyables.
|
||||
*
|
||||
* @param mixed $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isMulti($item)
|
||||
{
|
||||
if (!is_array($item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_array(head($item)) || head($item) instanceof Buyable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $identifier
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function storedCartInstanceWithIdentifierExists($instance, $identifier)
|
||||
private function storedCartInstanceWithIdentifierExists(string $instance, string $identifier): bool
|
||||
{
|
||||
return $this->getConnection()->table($this->getTableName())->where(['identifier' => $identifier, 'instance'=> $instance])->exists();
|
||||
return $this->getConnection()->table(self::getTableName())->where(['identifier' => $identifier, 'instance'=> $instance])->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database connection.
|
||||
*
|
||||
* @return \Illuminate\Database\Connection
|
||||
*/
|
||||
private function getConnection()
|
||||
private function getConnection(): \Illuminate\Database\Connection
|
||||
{
|
||||
return app(DatabaseManager::class)->connection($this->getConnectionName());
|
||||
}
|
||||
@@ -811,48 +741,19 @@ class Cart
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getTableName()
|
||||
private static function getTableName(): string
|
||||
{
|
||||
return config('cart.database.table', 'shoppingcart');
|
||||
return Config::get('cart.database.table', 'shoppingcart');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the database connection name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getConnectionName()
|
||||
private function getConnectionName(): ?string
|
||||
{
|
||||
$connection = config('cart.database.connection');
|
||||
$connection = Config::get('cart.database.connection');
|
||||
|
||||
return is_null($connection) ? config('database.default') : $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Formatted number.
|
||||
*
|
||||
* @param $value
|
||||
* @param $decimals
|
||||
* @param $decimalPoint
|
||||
* @param $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function numberFormat($value, ?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
if (is_null($decimals)) {
|
||||
$decimals = config('cart.format.decimals', 2);
|
||||
}
|
||||
|
||||
if (is_null($decimalPoint)) {
|
||||
$decimalPoint = config('cart.format.decimal_point', '.');
|
||||
}
|
||||
|
||||
if (is_null($thousandSeperator)) {
|
||||
$thousandSeperator = config('cart.format.thousand_separator', ',');
|
||||
}
|
||||
|
||||
return number_format($value, $decimals, $decimalPoint, $thousandSeperator);
|
||||
return is_null($connection) ? Config::get('database.default') : $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -860,7 +761,7 @@ class Cart
|
||||
*
|
||||
* @return \Carbon\Carbon|null
|
||||
*/
|
||||
public function createdAt() : ?Carbon
|
||||
public function createdAt(): ?Carbon
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
@@ -870,7 +771,7 @@ class Cart
|
||||
*
|
||||
* @return \Carbon\Carbon|null
|
||||
*/
|
||||
public function updatedAt() : ?Carbon
|
||||
public function updatedAt(): ?Carbon
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
474
src/CartItem.php
474
src/CartItem.php
@@ -2,70 +2,47 @@
|
||||
|
||||
namespace Gloudemans\Shoppingcart;
|
||||
|
||||
use Gloudemans\Shoppingcart\Calculation\DefaultCalculator;
|
||||
use Gloudemans\Shoppingcart\Contracts\Buyable;
|
||||
use Gloudemans\Shoppingcart\Contracts\Calculator;
|
||||
use Gloudemans\Shoppingcart\Exceptions\InvalidCalculatorException;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Contracts\Support\Jsonable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Arr;
|
||||
use ReflectionClass;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Money\Currencies\ISOCurrencies;
|
||||
use Money\Formatter\DecimalMoneyFormatter;
|
||||
use Money\Money;
|
||||
|
||||
/**
|
||||
* @property-read mixed discount
|
||||
* @property-read float discountTotal
|
||||
* @property-read float priceTarget
|
||||
* @property-read float priceNet
|
||||
* @property-read float priceTotal
|
||||
* @property-read float subtotal
|
||||
* @property-read float taxTotal
|
||||
* @property-read float tax
|
||||
* @property-read float total
|
||||
* @property-read float priceTax
|
||||
*/
|
||||
class CartItem implements Arrayable, Jsonable
|
||||
{
|
||||
/**
|
||||
* The rowID of the cart item.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $rowId;
|
||||
public string $rowId;
|
||||
|
||||
/**
|
||||
* The ID of the cart item.
|
||||
*
|
||||
* @var int|string
|
||||
*/
|
||||
public $id;
|
||||
public int|string $id;
|
||||
|
||||
/**
|
||||
* The quantity for this cart item.
|
||||
*
|
||||
* @var int|float
|
||||
*/
|
||||
public $qty;
|
||||
public int $qty;
|
||||
|
||||
/**
|
||||
* The name of the cart item.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public string $name;
|
||||
|
||||
/**
|
||||
* The price without TAX of the cart item.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
public $price;
|
||||
public Money $price;
|
||||
|
||||
/**
|
||||
* The weight of the product.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
public $weight;
|
||||
public int $weight;
|
||||
|
||||
/**
|
||||
* The options for this cart item.
|
||||
@@ -74,240 +51,47 @@ class CartItem implements Arrayable, Jsonable
|
||||
|
||||
/**
|
||||
* The tax rate for the cart item.
|
||||
*
|
||||
* @var int|float
|
||||
*/
|
||||
public $taxRate = 0;
|
||||
public float $taxRate = 0;
|
||||
|
||||
/**
|
||||
* The FQN of the associated model.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
private $associatedModel = null;
|
||||
public ?string $associatedModel = null;
|
||||
|
||||
/**
|
||||
* The discount rate for the cart item.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
private $discountRate = 0;
|
||||
public float|Money $discount = 0;
|
||||
|
||||
/**
|
||||
* The cart instance of the cart item.
|
||||
*/
|
||||
public ?string $instance = null;
|
||||
|
||||
/**
|
||||
* CartItem constructor.
|
||||
*
|
||||
* @param int|string $id
|
||||
* @param string $name
|
||||
* @param float $price
|
||||
* @param float $weight
|
||||
* @param array $options
|
||||
*/
|
||||
public function __construct($id, string $name, $price, $weight = 0, array $options = [])
|
||||
public function __construct(int|string $id, string $name, Money $price, int $qty = 1, int $weight = 0, ?CartItemOptions $options = null)
|
||||
{
|
||||
if (empty($id)) {
|
||||
throw new \InvalidArgumentException('Please supply a valid identifier.');
|
||||
}
|
||||
if (empty($name)) {
|
||||
throw new \InvalidArgumentException('Please supply a valid name.');
|
||||
}
|
||||
if (strlen($price) < 0 || !is_numeric($price)) {
|
||||
throw new \InvalidArgumentException('Please supply a valid price.');
|
||||
}
|
||||
if (strlen($weight) < 0 || !is_numeric($weight)) {
|
||||
throw new \InvalidArgumentException('Please supply a valid weight.');
|
||||
}
|
||||
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->price = floatval($price);
|
||||
$this->weight = floatval($weight);
|
||||
$this->options = new CartItemOptions($options);
|
||||
$this->rowId = $this->generateRowId($id, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted weight.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function weight(int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
return $this->numberFormat($this->weight, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted price without TAX.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function price(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
return $this->numberFormat($this->price, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted price with discount applied.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function priceTarget(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
return $this->numberFormat($this->priceTarget, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted price with TAX.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function priceTax(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
return $this->numberFormat($this->priceTax, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted subtotal.
|
||||
* Subtotal is price for whole CartItem without TAX.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function subtotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
return $this->numberFormat($this->subtotal, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted total.
|
||||
* Total is price for whole CartItem with TAX.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function total(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
return $this->numberFormat($this->total, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted tax.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function tax(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
return $this->numberFormat($this->tax, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted tax.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function taxTotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
return $this->numberFormat($this->taxTotal, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted discount.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function discount(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
return $this->numberFormat($this->discount, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted total discount for this cart item.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function discountTotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
return $this->numberFormat($this->discountTotal, $decimals, $decimalPoint, $thousandSeperator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the formatted total price for this cart item.
|
||||
*
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function priceTotal(?int $decimals = null, ?string $decimalPoint = null, ?string $thousandSeperator = null)
|
||||
{
|
||||
return $this->numberFormat($this->priceTotal, $decimals, $decimalPoint, $thousandSeperator);
|
||||
$this->price = $price;
|
||||
$this->qty = $qty;
|
||||
$this->weight = $weight;
|
||||
$this->options = $options ?: new CartItemOptions([]);
|
||||
$this->rowId = $this->generateRowId($id, $options->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the quantity for this cart item.
|
||||
*
|
||||
* @param int|float $qty
|
||||
*/
|
||||
public function setQuantity($qty)
|
||||
public function setQuantity(int $qty)
|
||||
{
|
||||
if (empty($qty) || !is_numeric($qty)) {
|
||||
throw new \InvalidArgumentException('Please supply a valid quantity.');
|
||||
}
|
||||
|
||||
$this->qty = $qty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the cart item from a Buyable.
|
||||
*
|
||||
* @param \Gloudemans\Shoppingcart\Contracts\Buyable $item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateFromBuyable(Buyable $item)
|
||||
public function updateFromBuyable(Buyable $item): void
|
||||
{
|
||||
$this->id = $item->getBuyableIdentifier($this->options);
|
||||
$this->name = $item->getBuyableDescription($this->options);
|
||||
@@ -316,12 +100,8 @@ class CartItem implements Arrayable, Jsonable
|
||||
|
||||
/**
|
||||
* Update the cart item from an array.
|
||||
*
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateFromArray(array $attributes)
|
||||
public function updateFromArray(array $attributes): void
|
||||
{
|
||||
$this->id = Arr::get($attributes, 'id', $this->id);
|
||||
$this->qty = Arr::get($attributes, 'qty', $this->qty);
|
||||
@@ -337,10 +117,8 @@ class CartItem implements Arrayable, Jsonable
|
||||
* Associate the cart item with the given model.
|
||||
*
|
||||
* @param mixed $model
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public function associate($model)
|
||||
public function associate(string|Model $model): self
|
||||
{
|
||||
$this->associatedModel = is_string($model) ? $model : get_class($model);
|
||||
|
||||
@@ -349,12 +127,8 @@ class CartItem implements Arrayable, Jsonable
|
||||
|
||||
/**
|
||||
* Set the tax rate.
|
||||
*
|
||||
* @param int|float $taxRate
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public function setTaxRate($taxRate)
|
||||
public function setTaxRate(float $taxRate): self
|
||||
{
|
||||
$this->taxRate = $taxRate;
|
||||
|
||||
@@ -363,124 +137,119 @@ class CartItem implements Arrayable, Jsonable
|
||||
|
||||
/**
|
||||
* Set the discount rate.
|
||||
*
|
||||
* @param int|float $discountRate
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public function setDiscountRate($discountRate)
|
||||
public function setDiscount(float|Money $discount): self
|
||||
{
|
||||
$this->discountRate = $discountRate;
|
||||
$this->discount = $discount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cart instance.
|
||||
*
|
||||
* @param null|string $instance
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public function setInstance(?string $instance)
|
||||
public function setInstance(?string $instance): self
|
||||
{
|
||||
$this->instance = $instance;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attribute from the cart item or get the associated model.
|
||||
*
|
||||
* @param string $attribute
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($attribute)
|
||||
public function model(): ?Model
|
||||
{
|
||||
if (property_exists($this, $attribute)) {
|
||||
return $this->{$attribute};
|
||||
}
|
||||
$decimals = config('cart.format.decimals', 2);
|
||||
|
||||
switch ($attribute) {
|
||||
case 'model':
|
||||
if (isset($this->associatedModel)) {
|
||||
return with(new $this->associatedModel())->find($this->id);
|
||||
}
|
||||
// no break
|
||||
case 'modelFQCN':
|
||||
if (isset($this->associatedModel)) {
|
||||
return $this->associatedModel;
|
||||
}
|
||||
// no break
|
||||
case 'weightTotal':
|
||||
return round($this->weight * $this->qty, $decimals);
|
||||
if (isset($this->associatedModel)) {
|
||||
return (new $this->associatedModel())->find($this->id);
|
||||
}
|
||||
|
||||
$class = new ReflectionClass(config('cart.calculator', DefaultCalculator::class));
|
||||
if (!$class->implementsInterface(Calculator::class)) {
|
||||
throw new InvalidCalculatorException('The configured Calculator seems to be invalid. Calculators have to implement the Calculator Contract.');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return call_user_func($class->getName().'::getAttribute', $attribute, $this);
|
||||
/**
|
||||
* This will is the price of the CartItem considering the set quantity. If you need the single
|
||||
* price just set the parameter to true.
|
||||
*/
|
||||
public function price(): Money
|
||||
{
|
||||
return $this->price->multiply($this->qty);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the discount granted for this CartItem. It is based on the given price and, in case
|
||||
* discount is a float, multiplied or, in case it is an absolute Money, subtracted. It will return
|
||||
* a minimum value of 0.
|
||||
*/
|
||||
public function discount(): Money
|
||||
{
|
||||
if ($this->discount instanceof Money) {
|
||||
return $this->price()->subtract($this->discount);
|
||||
} else {
|
||||
return $this->price()->multiply(sprintf('%.14F', $this->discount), Config::get('cart.rounding', Money::ROUND_UP));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the final price of the CartItem but without any tax applied. This does on the
|
||||
* other hand include any discounts.
|
||||
*/
|
||||
public function subtotal(): Money
|
||||
{
|
||||
return Money::max(new Money(0, $this->price->getCurrency()), $this->price()->subtract($this->discount()));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the tax, based on the subtotal (all previous calculations) and set tax rate.
|
||||
*/
|
||||
public function tax(): Money
|
||||
{
|
||||
return $this->subtotal()->multiply(sprintf('%.14F', $this->taxRate), Config::get('cart.rounding', Money::ROUND_UP));
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the total price, consisting of the subtotal and tax applied.
|
||||
*/
|
||||
public function total(): Money
|
||||
{
|
||||
return $this->subtotal()->add($this->tax());
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the total price, consisting of the subtotal and tax applied.
|
||||
*/
|
||||
public function weight(): int
|
||||
{
|
||||
return $this->qty * $this->weight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance from a Buyable.
|
||||
*
|
||||
* @param \Gloudemans\Shoppingcart\Contracts\Buyable $item
|
||||
* @param array $options
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public static function fromBuyable(Buyable $item, array $options = [])
|
||||
public static function fromBuyable(Buyable $item, int $qty = 1, ?CartItemOptions $options = null): self
|
||||
{
|
||||
return new self($item->getBuyableIdentifier($options), $item->getBuyableDescription($options), $item->getBuyablePrice($options), $item->getBuyableWeight($options), $options);
|
||||
$options = $options ?: new CartItemOptions([]);
|
||||
|
||||
return new self($item->getBuyableIdentifier($options), $item->getBuyableDescription($options), $item->getBuyablePrice($options), $qty, $item->getBuyableWeight($options), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance from the given array.
|
||||
*
|
||||
* @param array $attributes
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public static function fromArray(array $attributes)
|
||||
public static function fromArray(array $attributes): self
|
||||
{
|
||||
$options = Arr::get($attributes, 'options', []);
|
||||
$options = new CartItemOptions(Arr::get($attributes, 'options', []));
|
||||
|
||||
return new self($attributes['id'], $attributes['name'], $attributes['price'], $attributes['weight'], $options);
|
||||
return new self($attributes['id'], $attributes['name'], $attributes['price'], $attributes['qty'], $attributes['weight'], $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance from the given attributes.
|
||||
*
|
||||
* @param int|string $id
|
||||
* @param string $name
|
||||
* @param float $price
|
||||
* @param array $options
|
||||
*
|
||||
* @return \Gloudemans\Shoppingcart\CartItem
|
||||
*/
|
||||
public static function fromAttributes($id, string $name, $price, $weight, array $options = [])
|
||||
public static function fromAttributes(int|string $id, string $name, Money $price, int $qty = 1, int $weight = 0, ?CartItemOptions $options = null): self
|
||||
{
|
||||
return new self($id, $name, $price, $weight, $options);
|
||||
}
|
||||
$options = $options ?: new CartItemOptions([]);
|
||||
|
||||
/**
|
||||
* Generate a unique id for the cart item.
|
||||
*
|
||||
* @param string $id
|
||||
* @param array $options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateRowId($id, array $options)
|
||||
{
|
||||
ksort($options);
|
||||
|
||||
return md5($id.serialize($options));
|
||||
return new self($id, $name, $price, $qty, $weight, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -494,15 +263,16 @@ class CartItem implements Arrayable, Jsonable
|
||||
'rowId' => $this->rowId,
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'price' => self::formatMoney($this->price),
|
||||
'qty' => $this->qty,
|
||||
'price' => $this->price,
|
||||
'weight' => $this->weight,
|
||||
'options' => is_object($this->options)
|
||||
? $this->options->toArray()
|
||||
: $this->options,
|
||||
'discount' => $this->discount,
|
||||
'tax' => $this->tax,
|
||||
'subtotal' => $this->subtotal,
|
||||
'options' => $this->options->toArray(),
|
||||
|
||||
/* Calculated attributes */
|
||||
'discount' => self::formatMoney($this->discount()),
|
||||
'subtotal' => self::formatMoney($this->subtotal()),
|
||||
'tax' => self::formatMoney($this->tax()),
|
||||
'total' => self::formatMoney($this->total()),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -519,40 +289,20 @@ class CartItem implements Arrayable, Jsonable
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the formatted number.
|
||||
*
|
||||
* @param float $value
|
||||
* @param int $decimals
|
||||
* @param string $decimalPoint
|
||||
* @param string $thousandSeperator
|
||||
*
|
||||
* @return string
|
||||
* Generate a unique id for the cart item.
|
||||
*/
|
||||
private function numberFormat($value, ?int $decimals, ?string $decimalPoint, ?string $thousandSeperator)
|
||||
private static function formatMoney(Money $money): string
|
||||
{
|
||||
if (is_null($decimals)) {
|
||||
$decimals = config('cart.format.decimals', 2);
|
||||
}
|
||||
|
||||
if (is_null($decimalPoint)) {
|
||||
$decimalPoint = config('cart.format.decimal_point', '.');
|
||||
}
|
||||
|
||||
if (is_null($thousandSeperator)) {
|
||||
$thousandSeperator = config('cart.format.thousand_separator', ',');
|
||||
}
|
||||
|
||||
return number_format($value, $decimals, $decimalPoint, $thousandSeperator);
|
||||
return (new DecimalMoneyFormatter(new ISOCurrencies()))->format($money);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the raw internal discount rate.
|
||||
* Should be used in calculators.
|
||||
*
|
||||
* @return float
|
||||
* Generate a unique id for the cart item.
|
||||
*/
|
||||
public function getDiscountRate()
|
||||
protected function generateRowId(string $id, array $options): string
|
||||
{
|
||||
return $this->discountRate;
|
||||
ksort($options);
|
||||
|
||||
return md5($id.serialize($options));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
|
||||
use Money\Money;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Gross price as base price
|
||||
| Rounding strategy
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This default value is used to select the method to calculate prices and taxes
|
||||
@@ -12,7 +14,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'calculator' => \Gloudemans\Shoppingcart\Calculation\DefaultCalculator::class,
|
||||
'rounding' => Money::ROUND_UP,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -24,7 +26,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'tax' => 21,
|
||||
'tax' => 0.21,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -44,18 +46,6 @@ return [
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Destroy the cart on user logout
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When this option is set to 'true' the cart will automatically
|
||||
| destroy all cart instances when the user logs out.
|
||||
|
|
||||
*/
|
||||
|
||||
'destroy_on_logout' => false,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default number format
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace Gloudemans\Shoppingcart\Contracts;
|
||||
|
||||
use Gloudemans\Shoppingcart\CartItemOptions;
|
||||
use Money\Money;
|
||||
|
||||
interface Buyable
|
||||
{
|
||||
/**
|
||||
@@ -9,24 +12,20 @@ interface Buyable
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
public function getBuyableIdentifier();
|
||||
public function getBuyableIdentifier(CartItemOptions $options);
|
||||
|
||||
/**
|
||||
* Get the description or title of the Buyable item.
|
||||
*/
|
||||
public function getBuyableDescription() : ?string;
|
||||
public function getBuyableDescription(CartItemOptions $options): ?string;
|
||||
|
||||
/**
|
||||
* Get the price of the Buyable item.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getBuyablePrice();
|
||||
public function getBuyablePrice(CartItemOptions $options): Money;
|
||||
|
||||
/**
|
||||
* Get the weight of the Buyable item.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getBuyableWeight();
|
||||
public function getBuyableWeight(CartItemOptions $options): int;
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Gloudemans\Shoppingcart\Contracts;
|
||||
|
||||
use Gloudemans\Shoppingcart\CartItem;
|
||||
|
||||
interface Calculator
|
||||
{
|
||||
public static function getAttribute(string $attribute, CartItem $cartItem);
|
||||
}
|
||||
@@ -13,8 +13,6 @@ interface InstanceIdentifier
|
||||
|
||||
/**
|
||||
* Get the unique identifier to load the Cart from.
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
public function getInstanceGlobalDiscount();
|
||||
public function getInstanceGlobalDiscount(): float;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class CreateShoppingcartTable extends Migration
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create(config('cart.database.table'), function (Blueprint $table) {
|
||||
Schema::create(Config::get('cart.database.table'), function (Blueprint $table) {
|
||||
$table->string('identifier');
|
||||
$table->string('instance');
|
||||
$table->longText('content');
|
||||
@@ -26,6 +26,6 @@ class CreateShoppingcartTable extends Migration
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop(config('cart.database.table'));
|
||||
Schema::drop(Config::get('cart.database.table'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Gloudemans\Shoppingcart\Exceptions;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class InvalidCalculatorException extends RuntimeException
|
||||
{
|
||||
}
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace Gloudemans\Shoppingcart;
|
||||
|
||||
use Illuminate\Auth\Events\Logout;
|
||||
use Illuminate\Session\SessionManager;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class ShoppingcartServiceProvider extends ServiceProvider
|
||||
@@ -15,19 +13,19 @@ class ShoppingcartServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
/* Bind Cart class to cart for Facade usage */
|
||||
$this->app->bind('cart', 'Gloudemans\Shoppingcart\Cart');
|
||||
|
||||
/* Determine where the config file is located */
|
||||
$config = __DIR__.'/Config/cart.php';
|
||||
|
||||
/* Use local config */
|
||||
$this->mergeConfigFrom($config, 'cart');
|
||||
|
||||
$this->publishes([__DIR__.'/Config/cart.php' => config_path('cart.php')], 'config');
|
||||
|
||||
$this->app['events']->listen(Logout::class, function () {
|
||||
if ($this->app['config']->get('cart.destroy_on_logout')) {
|
||||
$this->app->make(SessionManager::class)->forget('cart');
|
||||
}
|
||||
});
|
||||
/* Also allow publishing to overwrite local config */
|
||||
$this->publishes([$config => config_path('cart.php')], 'config');
|
||||
|
||||
/* Publish included migrations */
|
||||
$this->publishes([
|
||||
realpath(__DIR__.'/Database/migrations') => $this->app->databasePath().'/migrations',
|
||||
], 'migrations');
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
namespace Gloudemans\Tests\Shoppingcart;
|
||||
|
||||
use Gloudemans\Shoppingcart\CartItem;
|
||||
use Gloudemans\Shoppingcart\CartItemOptions;
|
||||
use Gloudemans\Shoppingcart\ShoppingcartServiceProvider;
|
||||
use Money\Currency;
|
||||
use Money\Money;
|
||||
use Orchestra\Testbench\TestCase;
|
||||
|
||||
class CartItemTest extends TestCase
|
||||
@@ -23,45 +26,35 @@ class CartItemTest extends TestCase
|
||||
/** @test */
|
||||
public function it_can_be_cast_to_an_array()
|
||||
{
|
||||
$cartItem = new CartItem(1, 'Some item', 10.00, 550, ['size' => 'XL', 'color' => 'red']);
|
||||
$cartItem->setQuantity(2);
|
||||
$cartItem = new CartItem(1, 'Some item', new Money(1000, new Currency('USD')), 2, 550, new CartItemOptions(['size' => 'XL', 'color' => 'red']));
|
||||
|
||||
$this->assertEquals([
|
||||
'id' => 1,
|
||||
'name' => 'Some item',
|
||||
'price' => 10.00,
|
||||
'price' => '10.00',
|
||||
'rowId' => '07d5da5550494c62daf9993cf954303f',
|
||||
'qty' => 2,
|
||||
'options' => [
|
||||
'size' => 'XL',
|
||||
'color' => 'red',
|
||||
],
|
||||
'tax' => 0,
|
||||
'subtotal' => 20.00,
|
||||
'discount' => 0.0,
|
||||
'weight' => 550.0,
|
||||
'tax' => '0.00',
|
||||
'subtotal' => '20.00',
|
||||
'total' => '20.00',
|
||||
'discount' => '0.00',
|
||||
'weight' => 550,
|
||||
], $cartItem->toArray());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_be_cast_to_json()
|
||||
{
|
||||
$cartItem = new CartItem(1, 'Some item', 10.00, 550, ['size' => 'XL', 'color' => 'red']);
|
||||
$cartItem->setQuantity(2);
|
||||
$cartItem = new CartItem(1, 'Some item', new Money(1000, new Currency('USD')), 2, 550, new CartItemOptions(['size' => 'XL', 'color' => 'red']));
|
||||
|
||||
$this->assertJson($cartItem->toJson());
|
||||
|
||||
$json = '{"rowId":"07d5da5550494c62daf9993cf954303f","id":1,"name":"Some item","qty":2,"price":10,"weight":550,"options":{"size":"XL","color":"red"},"discount":0,"tax":0,"subtotal":20}';
|
||||
$json = '{"rowId":"07d5da5550494c62daf9993cf954303f","id":1,"name":"Some item","price":"10.00","qty":2,"weight":550,"options":{"size":"XL","color":"red"},"discount":"0.00","subtotal":"20.00","tax":"0.00","total":"20.00"}';
|
||||
|
||||
$this->assertEquals($json, $cartItem->toJson());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_formats_price_total_correctly()
|
||||
{
|
||||
$cartItem = new CartItem(1, 'Some item', 10.00, 550, ['size' => 'XL', 'color' => 'red']);
|
||||
$cartItem->setQuantity(2);
|
||||
|
||||
$this->assertSame('20.00', $cartItem->priceTotal());
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,11 @@
|
||||
|
||||
namespace Gloudemans\Tests\Shoppingcart\Fixtures;
|
||||
|
||||
use Gloudemans\Shoppingcart\CartItemOptions;
|
||||
use Gloudemans\Shoppingcart\Contracts\Buyable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Money\Currency;
|
||||
use Money\Money;
|
||||
|
||||
class BuyableProduct extends Model implements Buyable
|
||||
{
|
||||
@@ -18,14 +21,16 @@ class BuyableProduct extends Model implements Buyable
|
||||
'title',
|
||||
'description',
|
||||
'price',
|
||||
'currency',
|
||||
'weight',
|
||||
];
|
||||
|
||||
protected $attributes = [
|
||||
'id' => 1,
|
||||
'name' => 'Item name',
|
||||
'price' => 10.00,
|
||||
'weight' => 0,
|
||||
'id' => 1,
|
||||
'name' => 'Item name',
|
||||
'price' => 1000,
|
||||
'currency' => 'USD',
|
||||
'weight' => 0,
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -33,7 +38,7 @@ class BuyableProduct extends Model implements Buyable
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
public function getBuyableIdentifier()
|
||||
public function getBuyableIdentifier(CartItemOptions $options)
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
@@ -43,27 +48,23 @@ class BuyableProduct extends Model implements Buyable
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBuyableDescription() : ?string
|
||||
public function getBuyableDescription(CartItemOptions $options): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price of the Buyable item.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getBuyablePrice()
|
||||
public function getBuyablePrice(CartItemOptions $options): Money
|
||||
{
|
||||
return $this->price;
|
||||
return new Money($this->price, new Currency($this->currency));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price of the Buyable item.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getBuyableWeight()
|
||||
public function getBuyableWeight(CartItemOptions $options): int
|
||||
{
|
||||
return $this->weight;
|
||||
}
|
||||
|
||||
@@ -20,13 +20,15 @@ class BuyableProductTrait extends Model implements Buyable
|
||||
'title',
|
||||
'description',
|
||||
'price',
|
||||
'currency',
|
||||
'weight',
|
||||
];
|
||||
|
||||
protected $attributes = [
|
||||
'id' => 1,
|
||||
'name' => 'Item name',
|
||||
'price' => 10.00,
|
||||
'weight' => 0,
|
||||
'id' => 1,
|
||||
'name' => 'Item name',
|
||||
'price' => 1000,
|
||||
'currency' => 'USD',
|
||||
'weight' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class Identifiable implements InstanceIdentifier
|
||||
*
|
||||
* @return int|string
|
||||
*/
|
||||
public function getInstanceGlobalDiscount()
|
||||
public function getInstanceGlobalDiscount(): float
|
||||
{
|
||||
return $this->discountRate;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
namespace Gloudemans\Tests\Shoppingcart\Fixtures;
|
||||
|
||||
class ProductModel
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProductModel extends Model
|
||||
{
|
||||
public $someValue = 'Some value';
|
||||
|
||||
public function find($id) : self
|
||||
public function find($id): self
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user