File: //home/armgroup/libs/app/Http/Livewire/Frontend/Checkout/Index.php
<?php
namespace App\Http\Livewire\Frontend\Checkout;
use App\Enums\DiscountableType;
use App\Enums\OrderPaymentType;
use App\Events\OrderSaved;
use App\Http\Livewire\ConvertEmptyStringsToNull;
use App\Http\Livewire\General;
use App\Models\Discount;
use App\Models\Order;
use App\Models\Product;
use Gloudemans\Shoppingcart\Facades\Cart;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
use Shetabit\Multipay\Invoice;
use Shetabit\Payment\Facade\Payment as shetabitPayment;
class Index extends Component
{
use ConvertEmptyStringsToNull, General;
public $cartTotal;
public $total;
public $description;
public $shipping_price = null;
public $shipping_type = null;
public $shippings = [];
public $delivery_date = null;
public $delivery_time = null;
public $address;
public $show_delivery = false;
public $discount_code = null;
public $courier_price = 0;
public $express_price = 0;
public $max_sending_days = 0;
public $discount_price = 0;
public $discount_id = null;
public $enable_dates = [];
public $enable_times = [];
public $action;
public $method;
public $inputs = [];
public function applyDiscountCode()
{
if ($this->discount_price || $this->discount_id) {
abort(403);
}
$this->validate([
'discount_code' => 'required|exists:discounts,code',
]);
$discount = Discount::where('code', $this->discount_code)->first();
if (($result = $discount->isValid($this->cartTotal, auth()->user())) !== 'OK') {
$this->addError('discount_code', $result);
return;
}
$items = Cart::content();
$include_products_total_price = $this->calculateDiscountableProductsPrice($discount, $items, Product::whereIn('id', $items->pluck('id')->toArray())->get());
if ($include_products_total_price <= 0) {
$this->addError('discount_code', 'هیچ محصولی که شامل این کد تخفیف باشد در سبد خرید شما وجود ندارد.');
return;
}
if ($discount->exceedMinOrderPriceLimitation($include_products_total_price)) {
$this->addError('discount_code', 'شما باید حداقل به میزان '.number_format($discount->minimum_order_price).' تومان از محصولات شامل کد تخفیف خرید کنید.');
return;
}
$this->discount_id = $discount->id;
$this->discount_price = $discount->calculateDiscountPrice($include_products_total_price);
$this->total = $this->calculateTotal();
}
private function calculateDiscountableProductsPrice($discount, $items, $products)
{
$include_products = [];
if (is_null($discount->discountable_items)) {
$include_products = $products->pluck('id')->toArray();
} else {
switch ($discount->discountable_type) {
case DiscountableType::PRODUCT:
$include_products = array_intersect($products->pluck('id')->toArray(), $discount->discountable_items);
break;
case DiscountableType::BRAND:
$include_products = array_intersect($products->pluck('brand_id')->toArray(), $discount->discountable_items);
break;
case DiscountableType::CATEGORY:
foreach ($products as $product) {
if (array_intersect($product->categories()->first()?->parents?->pluck('id')?->toArray(), $discount->discountable_items)) {
$include_products[] = $product->id;
}
}
break;
}
}
$result = 0;
foreach ($items as $item) {
if (in_array($item->id, $include_products)) {
$result += $item->price * $item->qty;
}
}
return $result;
}
public function cancelDiscountCode()
{
$this->discount_price = 0;
$this->discount_code = null;
$this->discount_id = null;
$this->total = $this->calculateTotal();
}
public function mount()
{
$this->max_sending_days = calculateMaxSendingDays();
if ($this->checkFreeShipping()) {
$this->shipping_price = 'رایگان';
} else {
if ($this->address->city->express_status) {
$this->shippings[] = $this->shipping_type = Order::SHIPPING_EXPRESS;
$this->shipping_price = $this->express_price = calculateShippingPrice($this->address->city, Order::SHIPPING_EXPRESS);
}
if ($this->address->city->courier_status) {
$this->shippings[] = $this->shipping_type = Order::SHIPPING_COURIER;
$this->shipping_price = $this->courier_price = calculateShippingPrice($this->address->city, Order::SHIPPING_COURIER);
}
}
$this->total = $this->calculateTotal();
if ($this->address->city->courier_status && $this->shipping_type !== Order::SHIPPING_EXPRESS) {
$now = now();
$disable_dates = DB::table('sending_days')
->where('day', '>', $now->clone()->format('Y-m-d'))
->pluck('day')
->toArray();
$count = 0;
$i = -1;
while ($count != 7) {
$date = $now->clone()->addDays($this->max_sending_days + $i++)->format('Y-m-d');
if (! in_array($date, $disable_dates)) {
$count++;
array_push($this->enable_dates, $date);
}
}
$this->enable_times = preg_split('/\r\n|\r|\n/', settings('shipping-enable-times'));
$this->show_delivery = true;
}
}
public function updatedShippingType($value)
{
if ($value == Order::SHIPPING_EXPRESS && $this->address->city->express_status) {
$this->show_delivery = false;
$this->shipping_price = $this->express_price;
} elseif ($value == Order::SHIPPING_COURIER && $this->address->city->courier_status) {
$this->show_delivery = true;
$this->shipping_price = $this->courier_price;
} else {
$this->ajaxDoneMessage('روش پست انتخاب شده اشتباه میباشد', 'error');
}
$this->total = $this->calculateTotal();
if ((! $this->checkFreeShipping()) && $this->shipping_price < 0) {
$this->ajaxDoneMessage('خطایی در محاسبه هزینه ارسال بوجود آمده، لطفا صفحه را رفرش کنید و در صورت برطرف نشدن مشکل بعدا تلاش کنید', 'error');
}
}
private function calculateTotal()
{
return $this->cartTotal + (is_numeric($this->shipping_price) ? $this->shipping_price : 0) - $this->discount_price;
}
protected function rules()
{
$rules['description'] = ['nullable', 'string', 'max:10000'];
if ($this->show_delivery) {
$rules['delivery_date'] = [
'required',
'date',
'in:'.implode(',', $this->enable_dates),
];
$rules['delivery_time'] = ['required', 'in:'.implode(',', $this->enable_times)];
}
return $rules;
}
public function save($payment_type)
{
if ($this->cartTotal != (int) Cart::subtotal(0, '', '')) {
$this->doneMessage('سبد خرید تغییر کرده کرده است لطفا مجددا اقدام کنید.', 'error');
return redirect()->route('checkout.index');
}
if (! checkSaleStatus()) {
$this->doneMessage('در حال حاضر فروش محصولات غیرفعال میباشد، لطفا بعدا امتحان کنید.', 'warning');
return redirect()->route('index');
}
if ($this->total != $this->calculateTotal()) {
$this->doneMessage('مشکلی در محاسبه هزینه سفارش پیش آمده، مجددا امتحان کنید.', 'error');
return redirect()->route('checkout.index');
}
if ((! in_array($payment_type, [OrderPaymentType::OFFLINE, OrderPaymentType::WALLET])) && $this->total < 500) {
$this->ajaxDoneMessage('امکان پرداخت آنلاین برای مبالغ کمتر از ۵۰۰ تومان نمیباشد', 'error');
return;
}
$items = Cart::content();
$products = Product::whereIn('id', $items->pluck('id')->toArray())->get();
if (! session()->has('address_id') || count($items) == 0) {
return redirect()->route('addresses.index');
}
$address = auth()->user()->addresses()->where('address_id', session('address_id'))->with(['city', 'city.province'])->first();
if (! $address) {
session()->forget('address_id');
return redirect()->route('addresses.index');
}
if ($this->checkPaymentType($payment_type)) {
$this->ajaxDoneMessage('لطفا روش پرداخت درست را انتخاب کنید!', 'error');
return;
}
if (! $this->checkFreeShipping()) {
// وقتی هزینه پست خطا برگردونه مثلا اگه api خطا بده و یا هر مورد دیگه یا روش تحویل رو انتخاب نکرده باشه
if ($this->shipping_price < 0 || is_null($this->shipping_type)) {
$this->ajaxDoneMessage('خطایی در محاسبه هزینه ارسال بوجود آمده، لطفا صفحه را رفرش کنید و در صورت برطرف نشدن مشکل بعدا تلاش کنید', 'error');
return;
}
// وقتی توی صفحه صروتحساب هست و قیمت ارسال رو مدیر تغییر داده یا از یه تب دیگه یه محصول جدید اضافه کرده و یا وقتی کاربر از یه تب دیگه استان و شهرش رو تغییر میده
if ($this->shipping_price != calculateShippingPrice($address->city, $this->shipping_type)) {
$this->doneMessage('هزینه ارسال تغییر کرده است، لطفا مجددا تلاش کنید.', 'error');
return redirect()->route('checkout.index');
}
}
// اینجا میتونم درستی تاریخ و زمان تحویل رو دوباره چک کنم ولی فک نکنم لزومی داشته باشه.
$this->validate();
foreach ($items as $item) {
$canBuyStatus = $products->find($item->id)->canBuy($item->qty, $item->options, $item->price);
if ($canBuyStatus !== 'OK') {
$this->doneMessage($canBuyStatus, 'error');
return redirect()->route('cart.index');
}
}
if (! $this->canUseDiscountCode($items, $products)) {
$this->doneMessage('مشکلی در ثبت کد تخفیف بوجود آمده است، لطفا کد خود را مجددا ثبت کنید.', 'error');
return redirect()->route('checkout.index');
}
DB::transaction(function () use ($payment_type, $items, $address, $products) {
$discount = 0;
$order_product_data = [];
$locked_stock_data = [];
foreach ($items as $item) {
$product = $products->find($item->id);
$product_discount = $item->name != 'none' ? ($item->name - $item->price) : 0;
$order_product_data[] = [
'product_id' => $product->id,
'option_id' => $item->options['option_id'] ?? null,
'value_id' => $item->options['value_id'] ?? null,
'quantity' => $item->qty,
'price' => $item->price,
'discount' => $product_discount,
'name' => $product->name.(isset($item->options['name']) ? (' ('.$item->options['name'].')') : ''),
];
$locked_stock_data[] = [
'product_id' => $product->id,
'option_id' => count($item->options) ? $item->options['option_id'] : null,
'value_id' => count($item->options) ? $item->options['value_id'] : null,
'quantity' => $item->qty,
];
$discount += ($product_discount * $item->qty);
}
$order = Order::create([
'user_id' => auth()->id(),
'city_id' => $this->address->city_id,
'discount_id' => $this->discount_id,
'tracking_code' => auth()->id().''.time(),
'description' => $this->description,
'price' => $this->total,
'discount' => $discount,
'discount_code_price' => $this->discount_price,
'shipping_price' => is_numeric($this->shipping_price) ? $this->shipping_price : 0,
'shipping_type' => $this->shipping_type,
'delivery_date' => $this->show_delivery ? $this->delivery_date : null,
'delivery_time' => $this->show_delivery ? $this->delivery_time : null,
'delivery_code' => $this->show_delivery ? Order::createDeliveryCode() : null,
'payment_type' => $payment_type,
]);
$order->products()->attach($order_product_data);
$order->addresses()->attach($address);
$order->lockedStocks()->createMany($locked_stock_data);
if (in_array($payment_type, [OrderPaymentType::OFFLINE, OrderPaymentType::WALLET])) {
OrderSaved::dispatch($order);
if ($payment_type == OrderPaymentType::WALLET) {
$order->wallets()->create([
'user_id' => auth()->id(),
'amount' => $order->price * -1,
'description' => "کسر بابت پرداخت سفارش {$order->tracking_code}",
]);
}
$this->doneMessage('سفارش شما با موفقیت ثبت شد.');
return redirect()->route('account.orders.show', $order);
} else {
$payment = $order->payments()->create([
'user_id' => auth()->id(),
'amount' => $order->price,
'ip' => request()->ip(),
]);
try {
$data = json_decode(shetabitPayment::callbackUrl(route('verify.order', $payment->id))->purchase(
(new Invoice)->amount($order->price),
function ($driver, $transactionId) use ($payment) {
$payment->update([
'transaction_id' => $transactionId,
'driver' => config('payment.default'), // اگر چند درگاه داشته باشد میتوانیم با via مشخص کنیم و مقدار را اینجا هم بزاریم
]);
}
)->pay()->toJson());
$this->action = $data->action;
$this->method = $data->method;
$this->inputs = (array) $data->inputs;
$this->dispatchBrowserEvent('submit-payment-form');
} catch (\Exception $exception) {
$payment->update([
'error_message' => $exception->getMessage(),
]);
$order->delete();
$this->doneMessage($exception->getMessage(), 'error');
return redirect()->route('cart.index');
}
}
});
}
public function canUseDiscountCode($items, $products): bool
{
if ($this->discount_price && $this->discount_id) {
$discount = Discount::where('code', $this->discount_code)->first();
if (! $discount || ($this->discount_id != $discount->id)) {
return false;
}
if ($discount->isValid($this->cartTotal, auth()->user()) !== 'OK') {
return false;
}
$include_products_total_price = $this->calculateDiscountableProductsPrice($discount, $items, $products);
if ($this->discount_price != $discount->calculateDiscountPrice($include_products_total_price)) {
return false;
}
if ($discount->exceedMinOrderPriceLimitation($include_products_total_price)) {
return false;
}
}
return true;
}
public function render()
{
return view('livewire.frontend.checkout.index');
}
private function checkPaymentType($payment_type): bool
{
return (settings('offline-status') != 1 && $payment_type == OrderPaymentType::OFFLINE) ||
(settings('online-status') != 1 && $payment_type == OrderPaymentType::ONLINE) ||
(settings('wallet-status') != 1 && $payment_type == OrderPaymentType::WALLET) ||
($payment_type == OrderPaymentType::WALLET && auth()->user()->credit < $this->total) ||
(! in_array($payment_type, OrderPaymentType::asArray()));
}
private function checkFreeShipping(): bool
{
return settings('shipping-free-price') && ($this->cartTotal > settings('shipping-free-price'));
}
}