¨4.0.1¨
This commit is contained in:
@@ -8,9 +8,14 @@ use Modules\Tax\Entities\TaxRate;
|
||||
use Illuminate\Support\Collection;
|
||||
use Modules\Coupon\Entities\Coupon;
|
||||
use Modules\Product\Entities\Product;
|
||||
use Modules\Product\Entities\ProductVariant;
|
||||
use Modules\Shipping\Facades\ShippingMethod;
|
||||
use Darryldecode\Cart\Cart as DarryldecodeCart;
|
||||
use Modules\Variation\Entities\VariationValue;
|
||||
use Modules\Product\Services\ChosenProductOptions;
|
||||
use Modules\Product\Services\ChosenProductVariations;
|
||||
use Darryldecode\Cart\Exceptions\InvalidItemException;
|
||||
use Darryldecode\Cart\Exceptions\InvalidConditionException;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
|
||||
class Cart extends DarryldecodeCart implements JsonSerializable
|
||||
@@ -20,51 +25,78 @@ class Cart extends DarryldecodeCart implements JsonSerializable
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function instance()
|
||||
public function instance(): static
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clear the cart.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear()
|
||||
public function clear(): void
|
||||
{
|
||||
parent::clear();
|
||||
|
||||
$this->clearCartConditions();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store a new item to the cart.
|
||||
*
|
||||
* @param int $productId
|
||||
* @param $variantId
|
||||
* @param int $qty
|
||||
* @param array $options
|
||||
*
|
||||
* @return void
|
||||
* @throws InvalidItemException
|
||||
*/
|
||||
public function store($productId, $qty, $options = [])
|
||||
public function store($productId, $variantId, $qty, $options = []): void
|
||||
{
|
||||
$options = array_filter($options);
|
||||
$variations = [];
|
||||
|
||||
$product = Product::with('files', 'categories', 'taxClass')->findOrFail($productId);
|
||||
$variant = ProductVariant::find($variantId);
|
||||
$item = $variant ?? $product;
|
||||
|
||||
if ($variant) {
|
||||
$uids = collect(explode('.', $variant->uids));
|
||||
$rawVariations = $uids->map(function ($uid) {
|
||||
return VariationValue::where('uid', $uid)->get()->pluck('id', 'variation.id');
|
||||
});
|
||||
|
||||
foreach ($rawVariations as $variation) {
|
||||
foreach ($variation as $variationId => $variationValueId) {
|
||||
$variations[$variationId] = $variationValueId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$chosenVariations = new ChosenProductVariations($product, $variations);
|
||||
$chosenOptions = new ChosenProductOptions($product, $options);
|
||||
|
||||
$this->add([
|
||||
'id' => md5("product_id.{$product->id}:options." . serialize($options)),
|
||||
'id' => md5("product_id.{$product->id}:options." . serialize($options) . "product_id.{$product->id}:variations." . serialize($variations)),
|
||||
'name' => $product->name,
|
||||
'price' => $product->selling_price->amount(),
|
||||
'price' => $item->selling_price->amount(),
|
||||
'quantity' => $qty,
|
||||
'attributes' => [
|
||||
'product' => $product,
|
||||
'variant' => $variant,
|
||||
'item' => $item,
|
||||
'variations' => $chosenVariations->getEntities(),
|
||||
'options' => $chosenOptions->getEntities(),
|
||||
'created_at' => time(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
public function updateQuantity($id, $qty)
|
||||
{
|
||||
$this->update($id, [
|
||||
@@ -75,6 +107,29 @@ class Cart extends DarryldecodeCart implements JsonSerializable
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Total quantity of the given cartItem
|
||||
* in the Cart.
|
||||
*
|
||||
* @param CartItem $cartItem
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function addedQty(CartItem $cartItem): int
|
||||
{
|
||||
$items = $this->items()->filter(function ($cartItemAlias) use ($cartItem) {
|
||||
if ($cartItem->variant && $cartItemAlias->variant) {
|
||||
return $cartItemAlias->variant->id === $cartItem->variant->id;
|
||||
}
|
||||
|
||||
return $cartItemAlias->product->id === $cartItem->product->id;
|
||||
});
|
||||
|
||||
return $items->sum('qty');
|
||||
}
|
||||
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->getContent()
|
||||
@@ -84,17 +139,6 @@ class Cart extends DarryldecodeCart implements JsonSerializable
|
||||
});
|
||||
}
|
||||
|
||||
public function addedQty($productId)
|
||||
{
|
||||
return $this->findByProductId($productId)->sum('qty');
|
||||
}
|
||||
|
||||
public function findByProductId($productId)
|
||||
{
|
||||
return $this->items()->filter(function ($cartItem) use ($productId) {
|
||||
return $cartItem->product->id == $productId;
|
||||
});
|
||||
}
|
||||
|
||||
public function crossSellProducts()
|
||||
{
|
||||
@@ -108,94 +152,36 @@ class Cart extends DarryldecodeCart implements JsonSerializable
|
||||
->flatten();
|
||||
}
|
||||
|
||||
public function getAllProducts()
|
||||
|
||||
public function getAllProducts(): EloquentCollection
|
||||
{
|
||||
return $this->items()
|
||||
->map(function ($cartItem) {
|
||||
return $cartItem->product;
|
||||
})
|
||||
->flatten()
|
||||
->pipe(function ($products) {
|
||||
return new EloquentCollection($products);
|
||||
});
|
||||
return $this->items()->map(function ($cartItem) {
|
||||
return $cartItem->product;
|
||||
})->flatten()->pipe(function ($products) {
|
||||
return new EloquentCollection($products);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function reduceStock()
|
||||
{
|
||||
$this->manageStock(function ($cartItem) {
|
||||
$cartItem->product->decrement('qty', $cartItem->qty);
|
||||
|
||||
if ($cartItem->refreshStock()->product->qty === 0) {
|
||||
$cartItem->product->markAsOutOfStock();
|
||||
}
|
||||
$cartItem->item->decrement('qty', $cartItem->qty);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function restoreStock()
|
||||
{
|
||||
$this->manageStock(function ($cartItem) {
|
||||
$cartItem->product->increment('qty', $cartItem->qty);
|
||||
|
||||
if ($cartItem->product->qty > 0) {
|
||||
$cartItem->product->markAsInStock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function manageStock($callback)
|
||||
{
|
||||
$this->items()
|
||||
->filter(function ($cartItem) {
|
||||
return $cartItem->product->manage_stock;
|
||||
})
|
||||
->each($callback);
|
||||
}
|
||||
|
||||
public function quantity()
|
||||
{
|
||||
return $this->getTotalQuantity();
|
||||
}
|
||||
|
||||
public function hasAvailableShippingMethod()
|
||||
{
|
||||
return $this->availableShippingMethods()->isNotEmpty();
|
||||
}
|
||||
|
||||
public function availableShippingMethods()
|
||||
{
|
||||
if ($this->allItemsAreVirtual()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return ShippingMethod::available();
|
||||
}
|
||||
|
||||
public function allItemsAreVirtual()
|
||||
{
|
||||
return $this->items()->every(function (CartItem $cartItem) {
|
||||
return $cartItem->product->is_virtual;
|
||||
});
|
||||
}
|
||||
|
||||
public function hasShippingMethod()
|
||||
{
|
||||
return $this->getConditionsByType('shipping_method')->isNotEmpty();
|
||||
}
|
||||
|
||||
public function shippingMethod()
|
||||
{
|
||||
if (!$this->hasShippingMethod()) {
|
||||
return new NullCartShippingMethod();
|
||||
}
|
||||
|
||||
return new CartShippingMethod($this, $this->getConditionsByType('shipping_method')->first());
|
||||
}
|
||||
|
||||
public function shippingCost()
|
||||
{
|
||||
return $this->shippingMethod()->cost();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidConditionException
|
||||
*/
|
||||
public function addShippingMethod($shippingMethod)
|
||||
{
|
||||
$this->removeShippingMethod();
|
||||
@@ -205,7 +191,7 @@ class Cart extends DarryldecodeCart implements JsonSerializable
|
||||
'name' => $shippingMethod->label,
|
||||
'type' => 'shipping_method',
|
||||
'target' => 'total',
|
||||
'value' => "+{$shippingMethod->cost->amount()}",
|
||||
'value' => $this->coupon()?->free_shipping ? 0 : $shippingMethod->cost->amount(),
|
||||
'order' => 1,
|
||||
'attributes' => [
|
||||
'shipping_method' => $shippingMethod,
|
||||
@@ -218,18 +204,26 @@ class Cart extends DarryldecodeCart implements JsonSerializable
|
||||
return $this->shippingMethod();
|
||||
}
|
||||
|
||||
|
||||
public function removeShippingMethod()
|
||||
{
|
||||
$this->removeConditionsByType('shipping_method');
|
||||
}
|
||||
|
||||
private function refreshFreeShippingCoupon()
|
||||
|
||||
public function coupon()
|
||||
{
|
||||
if ($this->coupon()->isFreeShipping()) {
|
||||
$this->applyCoupon($this->coupon()->entity());
|
||||
if (!$this->hasCoupon()) {
|
||||
return new NullCartCoupon();
|
||||
}
|
||||
|
||||
$couponCondition = $this->getConditionsByType('coupon')->first();
|
||||
$coupon = Coupon::with('products', 'categories')->find($couponCondition->getAttribute('coupon_id'));
|
||||
|
||||
return new CartCoupon($this, $coupon, $couponCondition);
|
||||
}
|
||||
|
||||
|
||||
public function hasCoupon()
|
||||
{
|
||||
if ($this->getConditionsByType('coupon')->isEmpty()) {
|
||||
@@ -243,69 +237,165 @@ class Cart extends DarryldecodeCart implements JsonSerializable
|
||||
return Coupon::where('id', $couponId)->exists();
|
||||
}
|
||||
|
||||
public function couponAlreadyApplied(Coupon $coupon)
|
||||
{
|
||||
return $this->coupon()->code() === $coupon->code;
|
||||
}
|
||||
|
||||
public function coupon()
|
||||
{
|
||||
if (!$this->hasCoupon()) {
|
||||
return new NullCartCoupon();
|
||||
}
|
||||
|
||||
$couponCondition = $this->getConditionsByType('coupon')->first();
|
||||
$coupon = Coupon::with('products', 'categories')->find($couponCondition->getAttribute('coupon_id'));
|
||||
|
||||
return new CartCoupon($this, $coupon, $couponCondition);
|
||||
}
|
||||
|
||||
public function discount()
|
||||
{
|
||||
return $this->coupon()->value();
|
||||
}
|
||||
|
||||
public function applyCoupon(Coupon $coupon)
|
||||
{
|
||||
$this->removeCoupon();
|
||||
|
||||
$this->condition(
|
||||
new CartCondition([
|
||||
'name' => $coupon->code,
|
||||
'type' => 'coupon',
|
||||
'target' => 'total',
|
||||
'value' => $this->getCouponValue($coupon),
|
||||
'order' => 2,
|
||||
'attributes' => [
|
||||
'coupon_id' => $coupon->id,
|
||||
],
|
||||
]),
|
||||
);
|
||||
try {
|
||||
$this->condition(
|
||||
new CartCondition([
|
||||
'name' => $coupon->code,
|
||||
'type' => 'coupon',
|
||||
'target' => 'total',
|
||||
'value' => $this->getCouponValue($coupon),
|
||||
'order' => 2,
|
||||
'attributes' => [
|
||||
'coupon_id' => $coupon->id,
|
||||
],
|
||||
]),
|
||||
);
|
||||
} catch (InvalidConditionException $e) {
|
||||
if (app()->hasDebugModeEnabled()) {
|
||||
$message = $e->getMessage();
|
||||
} else {
|
||||
$message = trans('core::something_went_wrong');
|
||||
}
|
||||
|
||||
if (request()->ajax()) {
|
||||
return response()->json(
|
||||
[
|
||||
'message' => $message,
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getCouponValue($coupon)
|
||||
{
|
||||
if ($coupon->free_shipping) {
|
||||
return "-{$this->shippingMethod()->cost()->amount()}";
|
||||
}
|
||||
|
||||
if ($coupon->is_percent) {
|
||||
return "-{$coupon->value}%";
|
||||
}
|
||||
|
||||
return "-{$coupon->value->amount()}";
|
||||
}
|
||||
|
||||
public function removeCoupon()
|
||||
{
|
||||
$this->removeConditionsByType('coupon');
|
||||
}
|
||||
|
||||
public function hasTax()
|
||||
|
||||
public function shippingMethod()
|
||||
{
|
||||
return $this->getConditionsByType('tax')->isNotEmpty();
|
||||
if (!$this->hasShippingMethod()) {
|
||||
return new NullCartShippingMethod();
|
||||
}
|
||||
|
||||
return new CartShippingMethod($this, $this->getConditionsByType('shipping_method')->first());
|
||||
}
|
||||
|
||||
|
||||
public function hasShippingMethod()
|
||||
{
|
||||
return $this->getConditionsByType('shipping_method')->isNotEmpty();
|
||||
}
|
||||
|
||||
|
||||
public function couponAlreadyApplied(Coupon $coupon)
|
||||
{
|
||||
return $this->coupon()->code() === $coupon->code;
|
||||
}
|
||||
|
||||
|
||||
public function discount()
|
||||
{
|
||||
return $this->coupon()->value();
|
||||
}
|
||||
|
||||
|
||||
public function addTaxes($request)
|
||||
{
|
||||
$this->removeTaxes();
|
||||
|
||||
$this->findTaxes($request)->each(/**
|
||||
* @throws InvalidConditionException
|
||||
*/
|
||||
function ($taxRate) {
|
||||
$this->condition(
|
||||
new CartCondition([
|
||||
'name' => $taxRate->id,
|
||||
'type' => 'tax',
|
||||
'target' => 'total',
|
||||
'value' => "+{$taxRate->rate}%",
|
||||
'order' => 3,
|
||||
'attributes' => [
|
||||
'tax_rate_id' => $taxRate->id,
|
||||
],
|
||||
]),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function removeTaxes()
|
||||
{
|
||||
$this->removeConditionsByType('tax');
|
||||
}
|
||||
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return json_encode($this->jsonSerialize());
|
||||
}
|
||||
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'items' => $this->items(),
|
||||
'quantity' => $this->getTotalQuantity(),
|
||||
'availableShippingMethods' => $this->availableShippingMethods(),
|
||||
'subTotal' => $this->subTotal(),
|
||||
'shippingMethodName' => $this->shippingMethod()->name(),
|
||||
'shippingCost' => $this->shippingCost(),
|
||||
'coupon' => $this->coupon(),
|
||||
'taxes' => $this->taxes(),
|
||||
'total' => $this->total(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function availableShippingMethods(): Collection
|
||||
{
|
||||
if ($this->allItemsAreVirtual()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return ShippingMethod::available();
|
||||
}
|
||||
|
||||
|
||||
public function allItemsAreVirtual()
|
||||
{
|
||||
return $this->items()->every(function (CartItem $cartItem) {
|
||||
return $cartItem->product->is_virtual;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function subTotal()
|
||||
{
|
||||
return Money::inDefaultCurrency($this->getSubTotal())->add($this->optionsPrice());
|
||||
}
|
||||
|
||||
|
||||
public function shippingCost()
|
||||
{
|
||||
return $this->shippingMethod()->cost();
|
||||
}
|
||||
|
||||
|
||||
public function taxes()
|
||||
{
|
||||
if (!$this->hasTax()) {
|
||||
@@ -322,50 +412,56 @@ class Cart extends DarryldecodeCart implements JsonSerializable
|
||||
});
|
||||
}
|
||||
|
||||
private function getTaxRateIds($taxConditions)
|
||||
|
||||
public function hasTax()
|
||||
{
|
||||
return $taxConditions->map(function ($taxCondition) {
|
||||
return $taxCondition->getAttribute('tax_rate_id');
|
||||
});
|
||||
return $this->getConditionsByType('tax')->isNotEmpty();
|
||||
}
|
||||
|
||||
|
||||
public function total()
|
||||
{
|
||||
return $this->subTotal()
|
||||
->add($this->shippingMethod()->cost())
|
||||
->subtract($this->coupon()->value())
|
||||
->add($this->tax());
|
||||
}
|
||||
|
||||
|
||||
public function tax()
|
||||
{
|
||||
return Money::inDefaultCurrency($this->calculateTax());
|
||||
}
|
||||
|
||||
private function calculateTax()
|
||||
|
||||
private function manageStock($callback)
|
||||
{
|
||||
return $this->taxes()->sum(function ($cartTax) {
|
||||
return $cartTax->amount()->amount();
|
||||
});
|
||||
$this->items()
|
||||
->filter(function ($cartItem) {
|
||||
return $cartItem->item->manage_stock;
|
||||
})
|
||||
->each($callback);
|
||||
}
|
||||
|
||||
public function addTaxes($request)
|
||||
{
|
||||
$this->removeTaxes();
|
||||
|
||||
$this->findTaxes($request)->each(function ($taxRate) {
|
||||
$this->condition(
|
||||
new CartCondition([
|
||||
'name' => $taxRate->id,
|
||||
'type' => 'tax',
|
||||
'target' => 'total',
|
||||
'value' => "+{$taxRate->rate}%",
|
||||
'order' => 3,
|
||||
'attributes' => [
|
||||
'tax_rate_id' => $taxRate->id,
|
||||
],
|
||||
]),
|
||||
);
|
||||
});
|
||||
private function refreshFreeShippingCoupon()
|
||||
{
|
||||
if ($this->coupon()->isFreeShipping()) {
|
||||
$this->applyCoupon($this->coupon()->entity());
|
||||
}
|
||||
}
|
||||
|
||||
public function removeTaxes()
|
||||
|
||||
private function getCouponValue($coupon)
|
||||
{
|
||||
$this->removeConditionsByType('tax');
|
||||
if ($coupon->is_percent) {
|
||||
return "-{$coupon->value}%";
|
||||
}
|
||||
|
||||
return "-{$coupon->value->amount()}";
|
||||
}
|
||||
|
||||
|
||||
private function findTaxes($request)
|
||||
{
|
||||
return $this->items()
|
||||
@@ -377,16 +473,13 @@ class Cart extends DarryldecodeCart implements JsonSerializable
|
||||
->filter();
|
||||
}
|
||||
|
||||
public function subTotal()
|
||||
{
|
||||
return Money::inDefaultCurrency($this->getSubTotal())->add($this->optionsPrice());
|
||||
}
|
||||
|
||||
private function optionsPrice()
|
||||
{
|
||||
return Money::inDefaultCurrency($this->calculateOptionsPrice());
|
||||
}
|
||||
|
||||
|
||||
private function calculateOptionsPrice()
|
||||
{
|
||||
return $this->items()->sum(function ($cartItem) {
|
||||
@@ -397,36 +490,19 @@ class Cart extends DarryldecodeCart implements JsonSerializable
|
||||
});
|
||||
}
|
||||
|
||||
public function total()
|
||||
|
||||
private function getTaxRateIds($taxConditions)
|
||||
{
|
||||
return $this->subTotal()
|
||||
->add($this->shippingMethod()->cost())
|
||||
->subtract($this->coupon()->value())
|
||||
->add($this->tax());
|
||||
return $taxConditions->map(function ($taxCondition) {
|
||||
return $taxCondition->getAttribute('tax_rate_id');
|
||||
});
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'items' => $this->items(),
|
||||
'quantity' => $this->quantity(),
|
||||
'availableShippingMethods' => $this->availableShippingMethods(),
|
||||
'subTotal' => $this->subTotal(),
|
||||
'shippingMethodName' => $this->shippingMethod()->name(),
|
||||
'shippingCost' => $this->shippingCost(),
|
||||
'coupon' => $this->coupon(),
|
||||
'taxes' => $this->taxes(),
|
||||
'total' => $this->total(),
|
||||
];
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
private function calculateTax()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return json_encode($this->jsonSerialize());
|
||||
return $this->taxes()->sum(function ($cartTax) {
|
||||
return $cartTax->amount()->amount();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class CartCoupon implements JsonSerializable
|
||||
private $coupon;
|
||||
private $couponCondition;
|
||||
|
||||
|
||||
public function __construct($cart, $coupon, $couponCondition)
|
||||
{
|
||||
$this->cart = $cart;
|
||||
@@ -18,74 +19,111 @@ class CartCoupon implements JsonSerializable
|
||||
$this->couponCondition = $couponCondition;
|
||||
}
|
||||
|
||||
|
||||
public function entity()
|
||||
{
|
||||
return $this->coupon;
|
||||
}
|
||||
|
||||
|
||||
public function id()
|
||||
{
|
||||
return $this->coupon->id;
|
||||
}
|
||||
|
||||
public function code()
|
||||
{
|
||||
return $this->coupon->code;
|
||||
}
|
||||
|
||||
public function isFreeShipping()
|
||||
{
|
||||
return $this->coupon->free_shipping;
|
||||
}
|
||||
|
||||
|
||||
public function usageLimitReached($customerEmail)
|
||||
{
|
||||
return $this->fresh()->coupon->usageLimitReached($customerEmail);
|
||||
}
|
||||
|
||||
public function didNotSpendTheRequiredAmount()
|
||||
{
|
||||
return $this->fresh()->coupon->didNotSpendTheRequiredAmount();
|
||||
}
|
||||
|
||||
public function spentMoreThanMaximumAmount()
|
||||
{
|
||||
return $this->fresh()->coupon->spentMoreThanMaximumAmount();
|
||||
}
|
||||
|
||||
public function fresh()
|
||||
public function fresh(): static
|
||||
{
|
||||
$this->coupon = $this->coupon->refresh();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function didNotSpendTheRequiredAmount()
|
||||
{
|
||||
return $this->fresh()->coupon->didNotSpendTheRequiredAmount();
|
||||
}
|
||||
|
||||
|
||||
public function spentMoreThanMaximumAmount()
|
||||
{
|
||||
return $this->fresh()->coupon->spentMoreThanMaximumAmount();
|
||||
}
|
||||
|
||||
|
||||
public function usedOnce()
|
||||
{
|
||||
$this->coupon->increment('used');
|
||||
}
|
||||
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return json_encode($this->jsonSerialize());
|
||||
}
|
||||
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'code' => $this->code(),
|
||||
'value' => $this->value(),
|
||||
'free_shipping' => $this->coupon->free_shipping,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function code()
|
||||
{
|
||||
return $this->coupon->code;
|
||||
}
|
||||
|
||||
|
||||
public function value()
|
||||
{
|
||||
if ($this->coupon->free_shipping) {
|
||||
return $this->cart->shippingMethod()->cost();
|
||||
}
|
||||
|
||||
return Money::inDefaultCurrency($this->calculate());
|
||||
}
|
||||
|
||||
private function calculate()
|
||||
|
||||
public function __get($attribute)
|
||||
{
|
||||
return $this->couponCondition->getCalculatedValue($this->productsTotalPrice());
|
||||
return $this->coupon->{$attribute};
|
||||
}
|
||||
|
||||
private function productsTotalPrice()
|
||||
|
||||
private function calculate()
|
||||
{
|
||||
return $this->couponCondition->getCalculatedValue($this->couponApplicableProductsTotalPrice());
|
||||
}
|
||||
|
||||
|
||||
private function couponApplicableProductsTotalPrice()
|
||||
{
|
||||
return $this->couponApplicableProducts()->sum(function ($cartItem) {
|
||||
return $cartItem->total()->amount();
|
||||
return $cartItem->totalPrice()->amount();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private function couponApplicableProducts()
|
||||
{
|
||||
return $this->cart->items()->filter(function ($cartItem) {
|
||||
@@ -99,6 +137,7 @@ class CartCoupon implements JsonSerializable
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private function inApplicableProducts($cartItem)
|
||||
{
|
||||
if ($this->coupon->products->isEmpty()) {
|
||||
@@ -108,6 +147,7 @@ class CartCoupon implements JsonSerializable
|
||||
return $this->coupon->products->contains($cartItem->product);
|
||||
}
|
||||
|
||||
|
||||
private function inExcludedProducts($cartItem)
|
||||
{
|
||||
if ($this->coupon->excludeProducts->isEmpty()) {
|
||||
@@ -117,6 +157,7 @@ class CartCoupon implements JsonSerializable
|
||||
return $this->coupon->excludeProducts->contains($cartItem->product);
|
||||
}
|
||||
|
||||
|
||||
private function inApplicableCategories($cartItem)
|
||||
{
|
||||
if ($this->coupon->categories->isEmpty()) {
|
||||
@@ -126,6 +167,7 @@ class CartCoupon implements JsonSerializable
|
||||
return $this->coupon->categories->intersect($cartItem->product->categories)->isNotEmpty();
|
||||
}
|
||||
|
||||
|
||||
private function inExcludedCategories($cartItem)
|
||||
{
|
||||
if ($this->coupon->excludeCategories->isEmpty()) {
|
||||
@@ -134,27 +176,4 @@ class CartCoupon implements JsonSerializable
|
||||
|
||||
return $this->coupon->excludeCategories->intersect($cartItem->product->categories)->isNotEmpty();
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'code' => $this->code(),
|
||||
'value' => $this->value(),
|
||||
];
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return json_encode($this->jsonSerialize());
|
||||
}
|
||||
|
||||
public function __get($attribute)
|
||||
{
|
||||
return $this->coupon->{$attribute};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,90 +2,127 @@
|
||||
|
||||
namespace Modules\Cart;
|
||||
|
||||
use stdClass;
|
||||
use JsonSerializable;
|
||||
use Modules\Support\Money;
|
||||
use Modules\Product\Entities\Product;
|
||||
use Modules\Product\Entities\ProductVariant;
|
||||
|
||||
|
||||
class CartItem implements JsonSerializable
|
||||
{
|
||||
public $id;
|
||||
public $qty;
|
||||
public $product;
|
||||
public $variant;
|
||||
public $item;
|
||||
public $options;
|
||||
public $variations;
|
||||
|
||||
|
||||
public function __construct($item)
|
||||
{
|
||||
$this->id = $item->id;
|
||||
$this->qty = $item->quantity;
|
||||
$this->product = $item->attributes['product'];
|
||||
$this->variant = $item->attributes['variant'];
|
||||
$this->item = $item->attributes['item'];
|
||||
$this->variations = $item->attributes['variations'];
|
||||
$this->options = $item->attributes['options'];
|
||||
}
|
||||
|
||||
public function unitPrice()
|
||||
{
|
||||
return $this->product->selling_price->add($this->optionsPrice());
|
||||
}
|
||||
|
||||
public function total()
|
||||
{
|
||||
return $this->unitPrice()->multiply($this->qty);
|
||||
}
|
||||
|
||||
public function optionsPrice()
|
||||
{
|
||||
return Money::inDefaultCurrency($this->calculateOptionsPrice());
|
||||
}
|
||||
|
||||
public function calculateOptionsPrice()
|
||||
{
|
||||
return $this->options->sum(function ($option) {
|
||||
return $this->valuesSum($option->values);
|
||||
});
|
||||
}
|
||||
|
||||
private function valuesSum($values)
|
||||
{
|
||||
return $values->sum(function ($value) {
|
||||
if ($value->price_type === 'fixed') {
|
||||
return $value->price->amount();
|
||||
}
|
||||
|
||||
return ($value->price / 100) * $this->product->selling_price->amount();
|
||||
});
|
||||
}
|
||||
|
||||
public function refreshStock()
|
||||
{
|
||||
$product = Product::select(['manage_stock', 'in_stock', 'qty'])->find($this->product->id);
|
||||
$item = $this->getItem();
|
||||
|
||||
$this->product->fill([
|
||||
'manage_stock' => $product->manage_stock,
|
||||
'in_stock' => $product->in_stock,
|
||||
'qty' => $product->qty,
|
||||
$this->item->fill([
|
||||
'manage_stock' => $item->manage_stock,
|
||||
'in_stock' => $item->in_stock,
|
||||
'qty' => $item->qty,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getItem()
|
||||
{
|
||||
if ($this->item instanceof ProductVariant) {
|
||||
return ProductVariant::addSelect('id', 'in_stock', 'manage_stock', 'qty')
|
||||
->where('id', $this->variant->id)
|
||||
->firstOrFail();
|
||||
}
|
||||
|
||||
return Product::withName()
|
||||
->addSelect('id', 'in_stock', 'manage_stock', 'qty')
|
||||
->where('id', $this->product->id)
|
||||
->firstOrFail();
|
||||
}
|
||||
|
||||
|
||||
public function findTax(array $addresses)
|
||||
{
|
||||
return $this->product->taxClass->findTaxRate($addresses);
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'qty' => $this->qty,
|
||||
'product' => $this->product->clean(),
|
||||
'options' => $this->options->keyBy('position'),
|
||||
'unitPrice' => $this->unitPrice(),
|
||||
'total' => $this->total(),
|
||||
];
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return json_encode($this->jsonSerialize());
|
||||
}
|
||||
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'qty' => $this->qty,
|
||||
'product' => $this->product->clean(),
|
||||
'variant' => $this->variant?->clean(),
|
||||
'item' => $this->item,
|
||||
'variations' => $this->variations->isNotEmpty() ? $this->variations->keyBy('position') : new stdClass,
|
||||
'options' => $this->options->isNotEmpty() ? $this->options->keyBy('position') : new stdClass,
|
||||
'unitPrice' => $this->unitPrice(),
|
||||
'total' => $this->totalPrice(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function unitPrice()
|
||||
{
|
||||
return $this->item->selling_price->add($this->optionsPrice());
|
||||
}
|
||||
|
||||
|
||||
public function optionsPrice(): Money
|
||||
{
|
||||
return Money::inDefaultCurrency($this->calculateOptionsPrice());
|
||||
}
|
||||
|
||||
|
||||
public function totalPrice()
|
||||
{
|
||||
return $this->unitPrice()->multiply($this->qty);
|
||||
}
|
||||
|
||||
|
||||
private function calculateOptionsPrice()
|
||||
{
|
||||
return $this->options->sum(function ($option) {
|
||||
return $this->sumOfTheValuesOf($option);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private function sumOfTheValuesOf($option)
|
||||
{
|
||||
return $option->values->sum(function ($value) {
|
||||
if ($value->price_type === 'fixed') {
|
||||
return $value->price->amount();
|
||||
}
|
||||
|
||||
return ($value->price / 100) * $this->item->selling_price->amount();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,27 +9,32 @@ class CartShippingMethod
|
||||
private $cart;
|
||||
private $shippingMethodCondition;
|
||||
|
||||
|
||||
public function __construct($cart, $shippingMethodCondition)
|
||||
{
|
||||
$this->cart = $cart;
|
||||
$this->shippingMethodCondition = $shippingMethodCondition;
|
||||
}
|
||||
|
||||
|
||||
public function name()
|
||||
{
|
||||
return $this->shippingMethodCondition->getAttribute('shipping_method')->name;
|
||||
}
|
||||
|
||||
|
||||
public function label()
|
||||
{
|
||||
return $this->shippingMethodCondition->getAttribute('shipping_method')->label;
|
||||
}
|
||||
|
||||
|
||||
public function cost()
|
||||
{
|
||||
return Money::inDefaultCurrency($this->calculate());
|
||||
}
|
||||
|
||||
|
||||
private function calculate()
|
||||
{
|
||||
return $this->shippingMethodCondition->getCalculatedValue($this->cart->subTotal()->amount());
|
||||
|
||||
@@ -11,6 +11,7 @@ class CartTax implements JsonSerializable
|
||||
private $taxRate;
|
||||
private $taxCondition;
|
||||
|
||||
|
||||
public function __construct($cart, $taxRate, $taxCondition)
|
||||
{
|
||||
$this->cart = $cart;
|
||||
@@ -18,33 +19,60 @@ class CartTax implements JsonSerializable
|
||||
$this->taxCondition = $taxCondition;
|
||||
}
|
||||
|
||||
|
||||
public function id()
|
||||
{
|
||||
return $this->taxRate->id;
|
||||
}
|
||||
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return json_encode($this->jsonSerialize());
|
||||
}
|
||||
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name(),
|
||||
'amount' => $this->amount(),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function name()
|
||||
{
|
||||
return $this->taxRate->name;
|
||||
}
|
||||
|
||||
public function amount()
|
||||
|
||||
public function amount(): Money
|
||||
{
|
||||
return Money::inDefaultCurrency($this->calculate());
|
||||
}
|
||||
|
||||
|
||||
private function calculate()
|
||||
{
|
||||
return $this->taxCondition->getCalculatedValue($this->productsTotalPrice());
|
||||
return $this->taxCondition->getCalculatedValue($this->taxApplicableProductsTotalPrice());
|
||||
}
|
||||
|
||||
private function productsTotalPrice()
|
||||
|
||||
private function taxApplicableProductsTotalPrice()
|
||||
{
|
||||
return $this->taxApplicableProducts()->sum(function ($cartItem) {
|
||||
return $cartItem->total()->amount();
|
||||
return $cartItem->totalPrice()->amount();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private function taxApplicableProducts()
|
||||
{
|
||||
return $this->cart->items()->filter(function ($cartItem) {
|
||||
@@ -52,26 +80,9 @@ class CartTax implements JsonSerializable
|
||||
});
|
||||
}
|
||||
|
||||
private function hasMatchingTaxClass($cartItem)
|
||||
|
||||
private function hasMatchingTaxClass($cartItem): bool
|
||||
{
|
||||
return $cartItem->product->tax_class_id === $this->taxRate->tax_class_id;
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'name' => $this->name(),
|
||||
'amount' => $this->amount(),
|
||||
];
|
||||
}
|
||||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return json_encode($this->jsonSerialize());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,15 +16,6 @@ return [
|
||||
|
||||
'thousands_sep' => env('SHOPPING_THOUSANDS_SEP', ','),
|
||||
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* persistence
|
||||
* ---------------------------------------------------------------
|
||||
*
|
||||
* the configuration for persisting cart
|
||||
*/
|
||||
'storage' => null,
|
||||
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* events
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateCartsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('carts', function (Blueprint $table) {
|
||||
$table->string('id')->index();
|
||||
$table->longText('data');
|
||||
$table->timestamps();
|
||||
|
||||
$table->primary('id');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('carts');
|
||||
}
|
||||
}
|
||||
29
Modules/Cart/Entities/Cart.php
Normal file
29
Modules/Cart/Entities/Cart.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Cart\Entities;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Cart extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'id', 'data',
|
||||
];
|
||||
|
||||
|
||||
public function setDataAttribute($value)
|
||||
{
|
||||
$this->attributes['data'] = serialize($value);
|
||||
}
|
||||
|
||||
|
||||
public function getDataAttribute($value)
|
||||
{
|
||||
return unserialize($value);
|
||||
}
|
||||
}
|
||||
10
Modules/Cart/Exceptions/CartItemsStockException.php
Normal file
10
Modules/Cart/Exceptions/CartItemsStockException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Cart\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class CartItemsStockException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
@@ -2,43 +2,54 @@
|
||||
|
||||
namespace Modules\Cart\Facades;
|
||||
|
||||
use Modules\Support\Money;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Cart\CartCoupon;
|
||||
use Modules\Shipping\Method;
|
||||
use Modules\Coupon\Entities\Coupon;
|
||||
use Modules\Cart\CartShippingMethod;
|
||||
use Darryldecode\Cart\CartCollection;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Darryldecode\Cart\CartConditionCollection;
|
||||
use Illuminate\Support\Collection as SupportCollection;
|
||||
|
||||
/**
|
||||
* @method static \Modules\Cart\Cart instance()
|
||||
* @method static void clear()
|
||||
* @method static void store(int $productId, int $qty, array $options = [])
|
||||
* @method static void store(int $productId, int $variantId, int $qty, array $options = [], array $variations = [])
|
||||
* @method static void updateQuantity(string $id, int $qty)
|
||||
* @method static \Darryldecode\Cart\CartCollection items()
|
||||
* @method static CartCollection items()
|
||||
* @method static int addedQty(int $productId)
|
||||
* @method static int findByProductId(int $productId)
|
||||
* @method static \Illuminate\Database\Eloquent\Collection crossSellProducts()
|
||||
* @method static \Illuminate\Database\Eloquent\Collection getAllProducts()
|
||||
* @method static crossSellProducts()
|
||||
* @method static Collection getAllProducts()
|
||||
* @method static void reduceStock()
|
||||
* @method static void restoreStock()
|
||||
* @method static int quantity()
|
||||
* @method static bool hasAvailableShippingMethod()
|
||||
* @method static \Illuminate\Support\Collection availableShippingMethods()
|
||||
* @method static SupportCollection availableShippingMethods()
|
||||
* @method static bool hasShippingMethod()
|
||||
* @method static \Modules\Cart\CartShippingMethod shippingMethod()
|
||||
* @method static \Modules\Support\Money shippingCost()
|
||||
* @method static \Modules\Support\Money addShippingMethod(\Modules\Shipping\Method $shippingMethod)
|
||||
* @method static CartShippingMethod shippingMethod()
|
||||
* @method static Money shippingCost()
|
||||
* @method static Money addShippingMethod(Method $shippingMethod)
|
||||
* @method static void removeShippingMethod()
|
||||
* @method static bool hasCoupon()
|
||||
* @method static bool couponAlreadyApplied()
|
||||
* @method static \Modules\Cart\CartCoupon coupon()
|
||||
* @method static \Modules\Support\Money discount()
|
||||
* @method static void applyCoupon(\Modules\Coupon\Entities\Coupon $coupon)
|
||||
* @method static CartCoupon coupon()
|
||||
* @method static Money discount()
|
||||
* @method static void applyCoupon(Coupon $coupon)
|
||||
* @method static void removeCoupon()
|
||||
* @method static void hasTax()
|
||||
* @method static \Darryldecode\Cart\CartConditionCollection taxes()
|
||||
* @method static \Modules\Support\Money tax()
|
||||
* @method static void addTaxes(\Illuminate\Http\Request $request)
|
||||
* @method static CartConditionCollection taxes()
|
||||
* @method static Money tax()
|
||||
* @method static void addTaxes(Request $request)
|
||||
* @method static void removeTaxes()
|
||||
* @method static \Modules\Support\Money subTotal()
|
||||
* @method static \Modules\Support\Money total()
|
||||
* @method static Money subTotal()
|
||||
* @method static Money total()
|
||||
* @method static array toArray()
|
||||
* @method static array jsonSerialize()
|
||||
* @method static bool remove(string $cartItemId)
|
||||
* @method static bool allItemsAreVirtual()
|
||||
*
|
||||
* @see \Modules\Cart\Cart
|
||||
*/
|
||||
@@ -49,7 +60,7 @@ class Cart extends Facade
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getFacadeAccessor()
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return \Modules\Cart\Cart::class;
|
||||
}
|
||||
|
||||
@@ -9,9 +9,9 @@ class CartClearController
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return \Modules\Cart\Cart
|
||||
*/
|
||||
public function store()
|
||||
public function store(): \Modules\Cart\Cart
|
||||
{
|
||||
Cart::clear();
|
||||
|
||||
|
||||
@@ -2,14 +2,18 @@
|
||||
|
||||
namespace Modules\Cart\Http\Controllers;
|
||||
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
|
||||
class CartController
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return Application|Factory|View
|
||||
*/
|
||||
public function index()
|
||||
public function index(): Application|Factory|View
|
||||
{
|
||||
return view('public.cart.index');
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
namespace Modules\Cart\Http\Controllers;
|
||||
|
||||
use Modules\Cart\Facades\Cart;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class CartCrossSellProductsController
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return Collection
|
||||
*/
|
||||
public function index()
|
||||
public function index(): Collection
|
||||
{
|
||||
return Cart::crossSellProducts();
|
||||
}
|
||||
|
||||
@@ -2,23 +2,43 @@
|
||||
|
||||
namespace Modules\Cart\Http\Controllers;
|
||||
|
||||
use Exception;
|
||||
use Modules\Cart\Facades\Cart;
|
||||
use Illuminate\Pipeline\Pipeline;
|
||||
use Illuminate\Routing\Controller;
|
||||
use Modules\Coupon\Entities\Coupon;
|
||||
use Modules\Coupon\Checkers\ValidCoupon;
|
||||
use Modules\Coupon\Checkers\MaximumSpend;
|
||||
use Modules\Coupon\Checkers\MinimumSpend;
|
||||
use Modules\Coupon\Checkers\CouponExists;
|
||||
use Modules\Coupon\Checkers\AlreadyApplied;
|
||||
use Modules\Coupon\Checkers\ExcludedProducts;
|
||||
use Modules\Coupon\Checkers\ApplicableProducts;
|
||||
use Modules\Coupon\Checkers\ExcludedCategories;
|
||||
use Modules\Coupon\Checkers\UsageLimitPerCoupon;
|
||||
use Modules\Cart\Http\Middleware\CheckItemStock;
|
||||
use Modules\Coupon\Checkers\ApplicableCategories;
|
||||
use Modules\Coupon\Checkers\UsageLimitPerCustomer;
|
||||
use Modules\Cart\Http\Requests\StoreCartItemRequest;
|
||||
use Modules\Coupon\Exceptions\MaximumSpendException;
|
||||
use Modules\Coupon\Exceptions\MinimumSpendException;
|
||||
use Modules\Cart\Http\Middleware\CheckProductIsInStock;
|
||||
use Modules\Cart\Http\Requests\UpdateCartItemRequest;
|
||||
|
||||
class CartItemController extends Controller
|
||||
{
|
||||
private $checkers = [
|
||||
private array $checkers = [
|
||||
CouponExists::class,
|
||||
AlreadyApplied::class,
|
||||
ValidCoupon::class,
|
||||
MinimumSpend::class,
|
||||
MaximumSpend::class,
|
||||
ApplicableProducts::class,
|
||||
ExcludedProducts::class,
|
||||
ApplicableCategories::class,
|
||||
ExcludedCategories::class,
|
||||
UsageLimitPerCoupon::class,
|
||||
UsageLimitPerCustomer::class,
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
@@ -26,54 +46,93 @@ class CartItemController extends Controller
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(CheckProductIsInStock::class)->only(['store', 'update']);
|
||||
$this->middleware(CheckItemStock::class)->only(['store', 'update']);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Modules\Cart\Http\Requests\StoreCartItemRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
* @param StoreCartItemRequest $request
|
||||
*
|
||||
* @return \Modules\Cart\Cart
|
||||
*/
|
||||
public function store(StoreCartItemRequest $request)
|
||||
{
|
||||
Cart::store($request->product_id, $request->qty, $request->options ?? []);
|
||||
Cart::store(
|
||||
$request->product_id,
|
||||
$request->variant_id,
|
||||
$request->qty,
|
||||
$request->options ?? []
|
||||
);
|
||||
|
||||
return Cart::instance();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param mixed $cartItemId
|
||||
* @return \Illuminate\Http\Response
|
||||
* @param UpdateCartItemRequest $request
|
||||
* @param string $cartItemId
|
||||
*
|
||||
* @return \Modules\Cart\Cart
|
||||
*/
|
||||
public function update($cartItemId)
|
||||
public function update(UpdateCartItemRequest $request, string $cartItemId)
|
||||
{
|
||||
Cart::updateQuantity($cartItemId, request('qty'));
|
||||
|
||||
try {
|
||||
resolve(Pipeline::class)
|
||||
->send(Cart::coupon())
|
||||
->through($this->checkers)
|
||||
->thenReturn();
|
||||
} catch (MinimumSpendException | MaximumSpendException $e) {
|
||||
Cart::removeCoupon();
|
||||
$cartWithCoupon = null;
|
||||
$couponCode = request()->query('coupon_code');
|
||||
if ($couponCode) {
|
||||
$coupon = Coupon::findByCode($couponCode);
|
||||
try {
|
||||
resolve(Pipeline::class)
|
||||
->send($coupon)
|
||||
->through($this->checkers)
|
||||
->then(function ($coupon) use (&$cartWithCoupon) {
|
||||
Cart::applyCoupon($coupon);
|
||||
$cartWithCoupon = json_encode(Cart::instance());
|
||||
Cart::removeCoupon();
|
||||
});
|
||||
} catch (Exception) {
|
||||
//Just suppressing the exception
|
||||
}
|
||||
}
|
||||
|
||||
return Cart::instance();
|
||||
return $cartWithCoupon ?? Cart::instance();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param string $cartItemId
|
||||
* @return \Illuminhtate\Http\Response
|
||||
*
|
||||
* @return \Modules\Cart\Cart
|
||||
*/
|
||||
public function destroy($cartItemId)
|
||||
public function destroy(string $cartItemId)
|
||||
{
|
||||
Cart::remove($cartItemId);
|
||||
|
||||
return Cart::instance();
|
||||
$cartWithCoupon = null;
|
||||
$couponCode = request()->query('coupon_code');
|
||||
if ($couponCode) {
|
||||
$coupon = Coupon::findByCode($couponCode);
|
||||
try {
|
||||
resolve(Pipeline::class)
|
||||
->send($coupon)
|
||||
->through($this->checkers)
|
||||
->then(function ($coupon) use (&$cartWithCoupon) {
|
||||
Cart::applyCoupon($coupon);
|
||||
$cartWithCoupon = json_encode(Cart::instance());
|
||||
Cart::removeCoupon();
|
||||
});
|
||||
} catch (Exception) {
|
||||
//Just suppressing the exception
|
||||
}
|
||||
}
|
||||
|
||||
return $cartWithCoupon ?? Cart::instance();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,44 @@
|
||||
|
||||
namespace Modules\Cart\Http\Controllers;
|
||||
|
||||
use Exception;
|
||||
use Modules\Cart\Facades\Cart;
|
||||
use Illuminate\Pipeline\Pipeline;
|
||||
use Modules\Coupon\Entities\Coupon;
|
||||
use Modules\Coupon\Checkers\ValidCoupon;
|
||||
use Modules\Coupon\Checkers\CouponExists;
|
||||
use Modules\Coupon\Checkers\MinimumSpend;
|
||||
use Modules\Coupon\Checkers\MaximumSpend;
|
||||
use Modules\Coupon\Checkers\AlreadyApplied;
|
||||
use Modules\Shipping\Facades\ShippingMethod;
|
||||
use Modules\Coupon\Checkers\ExcludedProducts;
|
||||
use Modules\Coupon\Checkers\ApplicableProducts;
|
||||
use Modules\Coupon\Checkers\ExcludedCategories;
|
||||
use Modules\Coupon\Checkers\UsageLimitPerCoupon;
|
||||
use Modules\Coupon\Checkers\ApplicableCategories;
|
||||
use Modules\Coupon\Checkers\UsageLimitPerCustomer;
|
||||
|
||||
class CartShippingMethodController
|
||||
{
|
||||
private array $checkers = [
|
||||
CouponExists::class,
|
||||
AlreadyApplied::class,
|
||||
ValidCoupon::class,
|
||||
MinimumSpend::class,
|
||||
MaximumSpend::class,
|
||||
ApplicableProducts::class,
|
||||
ExcludedProducts::class,
|
||||
ApplicableCategories::class,
|
||||
ExcludedCategories::class,
|
||||
UsageLimitPerCoupon::class,
|
||||
UsageLimitPerCustomer::class,
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return \Modules\Cart\Cart
|
||||
*/
|
||||
public function store()
|
||||
{
|
||||
@@ -18,6 +47,28 @@ class CartShippingMethodController
|
||||
|
||||
Cart::addShippingMethod($shippingMethod);
|
||||
|
||||
return Cart::instance();
|
||||
$cartWithCoupon = null;
|
||||
$couponCode = request()->query('coupon_code');
|
||||
|
||||
if ($couponCode) {
|
||||
$coupon = Coupon::findByCode($couponCode);
|
||||
|
||||
try {
|
||||
resolve(Pipeline::class)
|
||||
->send($coupon)
|
||||
->through($this->checkers)
|
||||
->then(function ($coupon) use (&$cartWithCoupon) {
|
||||
Cart::applyCoupon($coupon);
|
||||
|
||||
$cartWithCoupon = json_encode(Cart::instance());
|
||||
|
||||
Cart::removeCoupon();
|
||||
});
|
||||
} catch (Exception) {
|
||||
//Just suppressing the exception
|
||||
}
|
||||
}
|
||||
|
||||
return $cartWithCoupon ?? Cart::instance();
|
||||
}
|
||||
}
|
||||
|
||||
112
Modules/Cart/Http/Middleware/CheckCartItemsStock.php
Normal file
112
Modules/Cart/Http/Middleware/CheckCartItemsStock.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Cart\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Modules\Cart\CartItem;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Cart\Facades\Cart;
|
||||
use Modules\FlashSale\Entities\FlashSale;
|
||||
use Modules\Cart\Exceptions\CartItemsStockException;
|
||||
|
||||
class CheckCartItemsStock
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
try {
|
||||
Cart::items()->each(function (CartItem $cartItem) {
|
||||
$cartItem->refreshStock();
|
||||
$this->checkStock($cartItem);
|
||||
});
|
||||
} catch (CartItemsStockException $e) {
|
||||
if (request()->wantsJson()) {
|
||||
return response()->json(
|
||||
[
|
||||
'message' => $e->getMessage(),
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', $e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
if (app()->hasDebugModeEnabled()) {
|
||||
$message = $e->getMessage();
|
||||
} else {
|
||||
$message = trans('core::something_went_wrong');
|
||||
}
|
||||
|
||||
if (request()->ajax()) {
|
||||
return response()->json(
|
||||
[
|
||||
'message' => $message,
|
||||
],
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('error', $message);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
|
||||
public function isInStock($cartItem)
|
||||
{
|
||||
return $cartItem->item->isInStock();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws CartItemsStockException
|
||||
*/
|
||||
private function checkStock(CartItem $cartItem): void
|
||||
{
|
||||
if (!$this->isInStock($cartItem)) {
|
||||
throw new CartItemsStockException(trans('cart::messages.one_or_more_product_is_out_of_stock'));
|
||||
}
|
||||
|
||||
if (!$this->hasFlashSaleStock($cartItem)) {
|
||||
throw new CartItemsStockException(trans('cart::messages.one_or_more_product_doesn\'t_have_enough_stock'));
|
||||
}
|
||||
|
||||
if (!$this->hasStock($cartItem)) {
|
||||
throw new CartItemsStockException(trans('cart::messages.one_or_more_product_doesn\'t_have_enough_stock'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function hasFlashSaleStock(CartItem $cartItem): bool
|
||||
{
|
||||
if ($cartItem->variant || !FlashSale::contains($cartItem->product)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$remainingQty = FlashSale::remainingQty($cartItem->product);
|
||||
$addedCartQty = Cart::addedQty($cartItem);
|
||||
|
||||
return ($remainingQty - $addedCartQty) >= 0;
|
||||
}
|
||||
|
||||
|
||||
private function hasStock(CartItem $cartItem): bool
|
||||
{
|
||||
if (!$cartItem->item->manage_stock) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$addedCartQty = Cart::addedQty($cartItem);
|
||||
|
||||
return ($cartItem->item->qty - $addedCartQty) >= 0;
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,13 @@ class CheckCouponUsageLimit
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
*
|
||||
* @return mixed
|
||||
* @throws CouponUsageLimitReachedException
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
if (Cart::coupon()->usageLimitReached($request->customer_email)) {
|
||||
throw new CouponUsageLimitReachedException;
|
||||
|
||||
130
Modules/Cart/Http/Middleware/CheckItemStock.php
Normal file
130
Modules/Cart/Http/Middleware/CheckItemStock.php
Normal file
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Cart\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Modules\Cart\CartItem;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\Cart\Facades\Cart;
|
||||
use Modules\Product\Entities\Product;
|
||||
use Modules\FlashSale\Entities\FlashSale;
|
||||
use Modules\Product\Entities\ProductVariant;
|
||||
|
||||
class CheckItemStock
|
||||
{
|
||||
private CartItem|null $cartItem;
|
||||
private Product $product;
|
||||
private ProductVariant|null $variant;
|
||||
private Product|ProductVariant $item;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
if (request()->routeIs('cart.items.update')) {
|
||||
$this->cartItem = Cart::items()->get(request('cartItemId'));
|
||||
$this->product = $this->cartItem->product;
|
||||
$this->variant = $this->cartItem->variant;
|
||||
}
|
||||
|
||||
if (request()->routeIs('cart.items.store')) {
|
||||
$this->product = request('product_id') ? $this->getProduct(request('product_id')) : null;
|
||||
$this->variant = request('variant_id') ? $this->getVariant(request('variant_id')) : null;
|
||||
}
|
||||
|
||||
$this->item = $this->variant ?? $this->product;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): mixed
|
||||
{
|
||||
if ($this->item->isOutOfStock()) {
|
||||
abort(400, trans('cart::messages.out_of_stock'));
|
||||
}
|
||||
|
||||
|
||||
if (!$this->hasFlashSaleStock()) {
|
||||
abort(400, trans('cart::messages.not_have_enough_quantity_in_stock', [
|
||||
'stock' => FlashSale::remainingQty($this->product),
|
||||
]));
|
||||
}
|
||||
|
||||
|
||||
if (!$this->hasStock()) {
|
||||
abort(400, trans('cart::messages.not_have_enough_quantity_in_stock', [
|
||||
'stock' => $this->item->qty,
|
||||
]));
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
|
||||
private function getProduct($id)
|
||||
{
|
||||
return Product::withName()
|
||||
->addSelect('id', 'in_stock', 'manage_stock', 'qty')
|
||||
->where('id', $id)
|
||||
->firstOrFail();
|
||||
}
|
||||
|
||||
|
||||
private function getVariant($id)
|
||||
{
|
||||
return ProductVariant::addSelect('id', 'in_stock', 'manage_stock', 'qty')
|
||||
->where('id', $id)
|
||||
->firstOrFail();
|
||||
}
|
||||
|
||||
|
||||
private function hasFlashSaleStock(): bool
|
||||
{
|
||||
if (!FlashSale::contains($this->product)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$remainingQty = FlashSale::remainingQty($this->product);
|
||||
|
||||
$cartItem = Cart::items()->get(request('cartItemId'));
|
||||
|
||||
if ($cartItem) {
|
||||
$addedCartQty = Cart::addedQty($cartItem);
|
||||
// Exclude current cart item quantity from the total added cart quantity
|
||||
// So, that current quantity is not added with the updated quantity.
|
||||
$addedCartQty -= $cartItem->qty;
|
||||
|
||||
return ($remainingQty - $addedCartQty) >= request('qty');
|
||||
}
|
||||
|
||||
return $remainingQty >= request('qty');
|
||||
}
|
||||
|
||||
|
||||
private function hasStock(): bool
|
||||
{
|
||||
if (!$this->item->manage_stock) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$cartItem = Cart::items()->get(request('cartItemId'));
|
||||
|
||||
if ($cartItem) {
|
||||
$addedCartQty = Cart::addedQty($cartItem);
|
||||
|
||||
// Exclude current cart item quantity from the total added cart quantity
|
||||
// So, that current quantity is not added with the updated quantity.
|
||||
$addedCartQty -= $cartItem->qty;
|
||||
|
||||
return ($cartItem->item->qty - $addedCartQty) >= request('qty');
|
||||
}
|
||||
|
||||
return $this->item->qty >= request('qty');
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,9 @@ class RedirectIfCartIsEmpty
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param Request $request
|
||||
* @param Closure $next
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
|
||||
@@ -3,11 +3,41 @@
|
||||
namespace Modules\Cart\Http\Requests;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Modules\Option\Entities\Option;
|
||||
use Modules\Product\Entities\Product;
|
||||
use Modules\Core\Http\Requests\Request;
|
||||
use Modules\Product\Entities\ProductVariant;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class StoreCartItemRequest extends Request
|
||||
{
|
||||
|
||||
protected Product|null $product;
|
||||
protected ProductVariant|null $variant;
|
||||
protected Product|ProductVariant $item;
|
||||
|
||||
|
||||
public function __construct(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null)
|
||||
{
|
||||
parent::__construct($query, $request, $attributes, $cookies, $files, $server, $content);
|
||||
|
||||
$this->product = $this->getProduct();
|
||||
$this->variant = $this->getVariant();
|
||||
|
||||
$this->item = $this->variant ?? $this->product;
|
||||
}
|
||||
|
||||
|
||||
public function authorize()
|
||||
{
|
||||
if ($this->item->is_active) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
@@ -15,61 +45,11 @@ class StoreCartItemRequest extends Request
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
$product = Product::with('options')
|
||||
->select('id', 'manage_stock', 'qty')
|
||||
->findOrFail($this->product_id);
|
||||
|
||||
return array_merge([
|
||||
'qty' => ['required', 'numeric', $this->maxQty($product)],
|
||||
], $this->getOptionsRules($product->options));
|
||||
'qty' => ['required', 'numeric', $this->maxQty($this->item)],
|
||||
], $this->getOptionsRules($this->product->options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the max qty rule for the given product.
|
||||
*
|
||||
* @param \Modules\Product\Entities\Product $product
|
||||
* @return string|null
|
||||
*/
|
||||
private function maxQty($product)
|
||||
{
|
||||
if ($product->manage_stock) {
|
||||
return "max:{$product->qty}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rules for the given options.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Collection $options
|
||||
* @return array
|
||||
*/
|
||||
private function getOptionsRules($options)
|
||||
{
|
||||
return $options->flatMap(function ($option) {
|
||||
return ["options.{$option->id}" => $this->getOptionRules($option)];
|
||||
})->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rules for the given option.
|
||||
*
|
||||
* @param \Modules\Option\Entities\Option $option
|
||||
* @return array
|
||||
*/
|
||||
private function getOptionRules($option)
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if ($option->is_required) {
|
||||
$rules[] = 'required';
|
||||
}
|
||||
|
||||
if (in_array($option->type, ['dropdown', 'radio'])) {
|
||||
$rules[] = Rule::in($option->values->map->id->all());
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data to be validated from the request.
|
||||
@@ -86,6 +66,7 @@ class StoreCartItemRequest extends Request
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*
|
||||
@@ -98,4 +79,91 @@ class StoreCartItemRequest extends Request
|
||||
'options.*.in' => trans('cart::validation.the_selected_option_is_invalid'),
|
||||
], parent::messages());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'qty' => (int)$this->qty,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
private function getProduct()
|
||||
{
|
||||
$product_id = request()->input('product_id');
|
||||
|
||||
return Product::with('options')
|
||||
->select('id', 'manage_stock', 'qty', 'is_active')
|
||||
->find($product_id);
|
||||
}
|
||||
|
||||
|
||||
private function getVariant()
|
||||
{
|
||||
$variant_id = request()->input('variant_id');
|
||||
|
||||
if ($variant_id) {
|
||||
return ProductVariant::select('id', 'manage_stock', 'qty', 'is_active')
|
||||
->find($variant_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the max qty rule for the given product.
|
||||
*
|
||||
* @param Product|ProductVariant $item
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function maxQty(Product|ProductVariant $item)
|
||||
{
|
||||
if ($item->manage_stock) {
|
||||
return "max:{$item->qty}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get rules for the given options.
|
||||
*
|
||||
* @param Collection $options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getOptionsRules($options)
|
||||
{
|
||||
return $options->flatMap(function ($option) {
|
||||
return ["options.{$option->id}" => $this->getOptionRules($option)];
|
||||
})->all();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get rules for the given option.
|
||||
*
|
||||
* @param Option $option
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getOptionRules($option)
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
if ($option->is_required) {
|
||||
$rules[] = 'required';
|
||||
}
|
||||
|
||||
if (in_array($option->type, ['dropdown', 'radio'])) {
|
||||
$rules[] = Rule::in($option->values->map->id->all());
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
|
||||
65
Modules/Cart/Http/Requests/UpdateCartItemRequest.php
Normal file
65
Modules/Cart/Http/Requests/UpdateCartItemRequest.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Cart\Http\Requests;
|
||||
|
||||
use Modules\Cart\CartItem;
|
||||
use Modules\Cart\Facades\Cart;
|
||||
use Modules\Product\Entities\Product;
|
||||
use Modules\Core\Http\Requests\Request;
|
||||
use Modules\Product\Entities\ProductVariant;
|
||||
|
||||
class UpdateCartItemRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$item = $this->getCartItem()->item;
|
||||
|
||||
return [
|
||||
'qty' => ['required', 'numeric', $this->maxQty($item)],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'qty' => (int)$this->qty,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the item from the cart
|
||||
*
|
||||
* @return CartItem
|
||||
*/
|
||||
private function getCartItem(): CartItem
|
||||
{
|
||||
return Cart::items()->get($this->cartItemId)->refreshStock();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the max qty rule for the given product or variant.
|
||||
*
|
||||
* @param Product|ProductVariant $item
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function maxQty(Product|ProductVariant $item)
|
||||
{
|
||||
if ($item->manage_stock) {
|
||||
return "max:{$item->qty}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ class ClearCart
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
public function handle(): void
|
||||
{
|
||||
Cart::clear();
|
||||
}
|
||||
|
||||
@@ -6,46 +6,54 @@ use Modules\Support\Money;
|
||||
|
||||
class NullCartCoupon
|
||||
{
|
||||
public function id()
|
||||
public function id(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function code()
|
||||
|
||||
public function code(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function isFreeShipping()
|
||||
|
||||
public function isFreeShipping(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function usageLimitReached()
|
||||
|
||||
public function usageLimitReached(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function didNotSpendTheRequiredAmount()
|
||||
|
||||
public function didNotSpendTheRequiredAmount(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function spentMoreThanMaximumAmount()
|
||||
|
||||
public function spentMoreThanMaximumAmount(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function usedOnce()
|
||||
|
||||
public function usedOnce(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function value()
|
||||
|
||||
public function value(): Money
|
||||
{
|
||||
return Money::inDefaultCurrency(0);
|
||||
}
|
||||
|
||||
|
||||
public function __get($attribute)
|
||||
{
|
||||
//
|
||||
|
||||
@@ -11,12 +11,14 @@ class NullCartShippingMethod
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
public function title()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function cost()
|
||||
|
||||
public function cost(): Money
|
||||
{
|
||||
return Money::inDefaultCurrency(0);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Modules\Cart\Providers;
|
||||
|
||||
use Modules\Cart\Cart;
|
||||
use Modules\Cart\Storages\Database;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class CartServiceProvider extends ServiceProvider
|
||||
@@ -16,7 +17,7 @@ class CartServiceProvider extends ServiceProvider
|
||||
{
|
||||
$this->app->singleton(Cart::class, function ($app) {
|
||||
return new Cart(
|
||||
$app['session'],
|
||||
new Database(),
|
||||
$app['events'],
|
||||
'cart',
|
||||
session()->getId(),
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Modules\Cart\Providers;
|
||||
|
||||
use Modules\Cart\Listeners\ClearCart;
|
||||
use Modules\Checkout\Events\OrderPlaced;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
@@ -12,8 +14,8 @@ class EventServiceProvider extends ServiceProvider
|
||||
* @var array
|
||||
*/
|
||||
protected $listen = [
|
||||
\Modules\Checkout\Events\OrderPlaced::class => [
|
||||
\Modules\Cart\Listeners\ClearCart::class,
|
||||
OrderPlaced::class => [
|
||||
ClearCart::class,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'out_of_stock' => 'Sorry, the product is out of stock.',
|
||||
'not_have_enough_quantity_in_stock' => 'Sorry, we only have :stock in stock.',
|
||||
'one_or_more_product_is_out_of_stock' => 'Sorry, one or more product is out of stock.',
|
||||
'one_or_more_product_doesn\'t_have_enough_stock' => 'Sorry, one or more product doesn\'t have enough stock.',
|
||||
'out_of_stock' => 'Sorry, the product is out of stock',
|
||||
'not_have_enough_quantity_in_stock' => 'Sorry, we only have :stock in stock',
|
||||
'one_or_more_product_is_out_of_stock' => 'Sorry, one or more product is out of stock',
|
||||
'one_or_more_product_doesn\'t_have_enough_stock' => 'Sorry, one or more product doesn\'t have enough stock',
|
||||
];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
'this_field_is_required' => 'This field is required.',
|
||||
'the_selected_option_is_invalid' => 'The selected option is invalid.',
|
||||
'this_field_is_required' => 'This field is required',
|
||||
'the_selected_option_is_invalid' => 'The selected option is invalid',
|
||||
];
|
||||
|
||||
@@ -5,7 +5,7 @@ use Illuminate\Support\Facades\Route;
|
||||
Route::get('cart', 'CartController@index')->name('cart.index');
|
||||
|
||||
Route::post('cart/items', 'CartItemController@store')->name('cart.items.store');
|
||||
Route::put('cart/items/{cartItemId}', 'CartItemController@update')->name('cart.items.update');
|
||||
Route::any('cart/items/{cartItemId}', 'CartItemController@update')->name('cart.items.update');
|
||||
Route::delete('cart/items/{cartItemId}', 'CartItemController@destroy')->name('cart.items.destroy');
|
||||
|
||||
Route::post('cart/clear', 'CartClearController@store')->name('cart.clear.store');
|
||||
|
||||
48
Modules/Cart/Storages/Cache.php
Normal file
48
Modules/Cart/Storages/Cache.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Cart\Storages;
|
||||
|
||||
use Darryldecode\Cart\CartCollection;
|
||||
use Illuminate\Support\Facades\Cookie;
|
||||
|
||||
class Cache
|
||||
{
|
||||
private $data = [];
|
||||
private $cart_id;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->cart_id = Cookie::get('cart');
|
||||
if ($this->cart_id) {
|
||||
$this->data = Cache::get('cart_' . $this->cart_id);
|
||||
} else {
|
||||
$this->cart_id = uniqid();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function get($key)
|
||||
{
|
||||
return new CartCollection($this->data[$key] ?? []);
|
||||
}
|
||||
|
||||
|
||||
public function has($key)
|
||||
{
|
||||
return isset($this->data[$key]);
|
||||
}
|
||||
|
||||
|
||||
public function put($key, $value)
|
||||
{
|
||||
$this->data[$key] = $value;
|
||||
Cache::put('cart_' . $this->cart_id, $this->data);
|
||||
|
||||
if (!Cookie::hasQueued('cart')) {
|
||||
Cookie::queue(
|
||||
Cookie::make('cart', $this->cart_id, 60 * 24 * 30)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Modules/Cart/Storages/Database.php
Normal file
38
Modules/Cart/Storages/Database.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Cart\Storages;
|
||||
|
||||
use Modules\Cart\Entities\Cart;
|
||||
use Darryldecode\Cart\CartCollection;
|
||||
|
||||
class Database
|
||||
{
|
||||
public function get($key)
|
||||
{
|
||||
if ($this->has($key)) {
|
||||
return new CartCollection(Cart::find($key)->data);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function has($key)
|
||||
{
|
||||
return Cart::find($key);
|
||||
}
|
||||
|
||||
|
||||
public function put($key, $value)
|
||||
{
|
||||
if ($row = Cart::find($key)) {
|
||||
$row->data = $value;
|
||||
$row->save();
|
||||
} else {
|
||||
Cart::create([
|
||||
'id' => $key,
|
||||
'data' => $value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user