¨4.0.1¨

This commit is contained in:
¨NW¨
2023-12-03 14:07:47 +00:00
parent c08b36d1b6
commit f35052522d
1112 changed files with 43019 additions and 24987 deletions

View File

@@ -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();
});
}
}

View File

@@ -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};
}
}

View File

@@ -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();
});
}
}

View File

@@ -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());

View File

@@ -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());
}
}

View File

@@ -16,15 +16,6 @@ return [
'thousands_sep' => env('SHOPPING_THOUSANDS_SEP', ','),
/*
* ---------------------------------------------------------------
* persistence
* ---------------------------------------------------------------
*
* the configuration for persisting cart
*/
'storage' => null,
/*
* ---------------------------------------------------------------
* events

View File

@@ -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');
}
}

View 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);
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Modules\Cart\Exceptions;
use Exception;
class CartItemsStockException extends Exception
{
}

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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');
}

View File

@@ -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();
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View 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;
}
}

View File

@@ -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;

View 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');
}
}

View File

@@ -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)

View File

@@ -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;
}
}

View 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}";
}
}
}

View File

@@ -11,7 +11,7 @@ class ClearCart
*
* @return void
*/
public function handle()
public function handle(): void
{
Cart::clear();
}

View File

@@ -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)
{
//

View File

@@ -11,12 +11,14 @@ class NullCartShippingMethod
//
}
public function title()
{
//
}
public function cost()
public function cost(): Money
{
return Money::inDefaultCurrency(0);
}

View File

@@ -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(),

View File

@@ -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,
],
];
}

View File

@@ -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',
];

View File

@@ -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',
];

View File

@@ -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');

View 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)
);
}
}
}

View 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,
]);
}
}
}