Feat: Let's implement the coupons now
This commit is contained in:
parent
e03ac5dae0
commit
72f3decd3f
31 changed files with 1122 additions and 15 deletions
|
@ -3,9 +3,9 @@ root = true
|
|||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
|
@ -13,3 +13,24 @@ trim_trailing_whitespace = false
|
|||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
[docker-compose.yml]
|
||||
indent_size = 2
|
||||
|
||||
[*.php]
|
||||
indent_size = 4
|
||||
|
||||
[*.blade.php]
|
||||
indent_size = 2
|
||||
|
||||
[*.js]
|
||||
indent_size = 4
|
||||
|
||||
[*.jsx]
|
||||
indent_size = 2
|
||||
|
||||
[*.tsx]
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_size = 4
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,6 +10,7 @@ storage/debugbar
|
|||
.env.backup
|
||||
.idea
|
||||
.phpunit.result.cache
|
||||
.editorconfig
|
||||
docker-compose.override.yml
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
|
|
|
@ -10,6 +10,7 @@ use App\Models\PartnerDiscount;
|
|||
use App\Models\Payment;
|
||||
use App\Models\ShopProduct;
|
||||
use App\Models\User;
|
||||
use App\Models\Coupon;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Redirect;
|
||||
|
@ -41,6 +42,17 @@ class PayPalExtension extends AbstractExtension
|
|||
$user = Auth::user();
|
||||
$shopProduct = ShopProduct::findOrFail($request->shopProduct);
|
||||
$discount = PartnerDiscount::getDiscount();
|
||||
$discountPrice = $request->get('discountPrice');
|
||||
|
||||
dd($discountPrice);
|
||||
|
||||
// Partner Discount.
|
||||
$price = $shopProduct->price - ($shopProduct->price * $discount / 100);
|
||||
|
||||
// Coupon Discount.
|
||||
// if ($discountPrice) {
|
||||
// $price = $price - ($price * floatval($coupon_percentage) / 100);
|
||||
// }
|
||||
|
||||
// create a new payment
|
||||
$payment = Payment::create([
|
||||
|
@ -50,7 +62,7 @@ class PayPalExtension extends AbstractExtension
|
|||
'type' => $shopProduct->type,
|
||||
'status' => 'open',
|
||||
'amount' => $shopProduct->quantity,
|
||||
'price' => $shopProduct->price - ($shopProduct->price * $discount / 100),
|
||||
'price' => $price,
|
||||
'tax_value' => $shopProduct->getTaxValue(),
|
||||
'tax_percent' => $shopProduct->getTaxPercent(),
|
||||
'total_price' => $shopProduct->getTotalPrice(),
|
||||
|
@ -73,7 +85,7 @@ class PayPalExtension extends AbstractExtension
|
|||
'item_total' =>
|
||||
[
|
||||
'currency_code' => strtoupper($shopProduct->currency_code),
|
||||
'value' => $shopProduct->getPriceAfterDiscount(),
|
||||
'value' => number_format($price, 2),
|
||||
],
|
||||
'tax_total' =>
|
||||
[
|
||||
|
@ -86,7 +98,7 @@ class PayPalExtension extends AbstractExtension
|
|||
],
|
||||
"application_context" => [
|
||||
"cancel_url" => route('payment.Cancel'),
|
||||
"return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id]),
|
||||
"return_url" => route('payment.PayPalSuccess', ['payment' => $payment->id, 'couponCode' => $coupon_code]),
|
||||
'brand_name' => config('app.name', 'CtrlPanel.GG'),
|
||||
'shipping_preference' => 'NO_SHIPPING'
|
||||
]
|
||||
|
@ -126,6 +138,7 @@ class PayPalExtension extends AbstractExtension
|
|||
|
||||
$payment = Payment::findOrFail($laravelRequest->payment);
|
||||
$shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
|
||||
$coupon_code = $laravelRequest->input('couponCode');
|
||||
|
||||
$request = new OrdersCaptureRequest($laravelRequest->input('token'));
|
||||
$request->prefer('return=representation');
|
||||
|
@ -140,6 +153,12 @@ class PayPalExtension extends AbstractExtension
|
|||
'payment_id' => $response->result->id,
|
||||
]);
|
||||
|
||||
// increase the use of the coupon when the payment is confirmed.
|
||||
if ($coupon_code) {
|
||||
$coupon = new Coupon;
|
||||
$coupon->incrementUses($coupon_code);
|
||||
}
|
||||
|
||||
event(new UserUpdateCreditsEvent($user));
|
||||
event(new PaymentEvent($user, $payment, $shopProduct));
|
||||
|
||||
|
|
159
app/Http/Controllers/Admin/CouponController.php
Normal file
159
app/Http/Controllers/Admin/CouponController.php
Normal file
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Coupon;
|
||||
use App\Traits\Coupon as CouponTrait;
|
||||
use Illuminate\Http\Request;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class CouponController extends Controller
|
||||
{
|
||||
const READ_PERMISSION = "admin.coupons.read";
|
||||
const WRITE_PERMISSION = "admin.coupons.write";
|
||||
|
||||
use CouponTrait;
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->checkPermission(self::READ_PERMISSION);
|
||||
|
||||
return view('admin.coupons.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$this->checkPermission(self::WRITE_PERMISSION);
|
||||
|
||||
return view('admin.coupons.create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$coupon_code = $request->input('coupon_code');
|
||||
$coupon_type = $request->input('coupon_type');
|
||||
$coupon_value = $request->input('coupon_value');
|
||||
$coupon_max_uses = $request->input('coupon_uses');
|
||||
$coupon_datepicker = $request->input('datepicker');
|
||||
$random_codes_amount = $request->input('range_codes');
|
||||
$rules = [
|
||||
"coupon_type" => "required|string|in:percentage,amount",
|
||||
"coupon_uses" => "required|integer|digits_between:1,100",
|
||||
"coupon_value" => "required|numeric|between:0,100",
|
||||
"datepicker" => "required|date|after:" . Carbon::now()->format(Coupon::formatDate())
|
||||
];
|
||||
|
||||
// If for some reason you pass both fields at once.
|
||||
if ($coupon_code && $random_codes_amount) {
|
||||
return redirect()->back()->with('error', __('Only one of the two code inputs must be provided.'))->withInput($request->all());
|
||||
}
|
||||
|
||||
if (!$coupon_code && !$random_codes_amount) {
|
||||
return redirect()->back()->with('error', __('At least one of the two code inputs must be provided.'))->withInput($request->all());
|
||||
}
|
||||
|
||||
if ($coupon_code) {
|
||||
$rules['coupon_code'] = 'required|string|min:4';
|
||||
} elseif ($random_codes_amount) {
|
||||
$rules['range_codes'] = 'required|integer|digits_between:1,100';
|
||||
}
|
||||
|
||||
$request->validate($rules);
|
||||
|
||||
if (array_key_exists('range_codes', $rules)) {
|
||||
$data = [];
|
||||
$coupons = Coupon::generateRandomCoupon($random_codes_amount);
|
||||
|
||||
// Scroll through all the randomly generated coupons.
|
||||
foreach ($coupons as $coupon) {
|
||||
$data[] = [
|
||||
'code' => $coupon,
|
||||
'type' => $coupon_type,
|
||||
'value' => $coupon_value,
|
||||
'max_uses' => $coupon_max_uses,
|
||||
'expires_at' => $coupon_datepicker,
|
||||
'created_at' => Carbon::now(), // Does not fill in by itself when using the 'insert' method.
|
||||
'updated_at' => Carbon::now()
|
||||
];
|
||||
}
|
||||
Coupon::insert($data);
|
||||
} else {
|
||||
Coupon::create([
|
||||
'code' => $coupon_code,
|
||||
'type' => $coupon_type,
|
||||
'value' => $coupon_value,
|
||||
'max_uses' => $coupon_max_uses,
|
||||
'expires_at' => $coupon_datepicker,
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.coupons.index')->with('success', __("The coupon's was registered successfully."));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param \App\Models\Coupon $coupon
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(Coupon $coupon)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Coupon $coupon
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function edit(Coupon $coupon)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \App\Models\Coupon $coupon
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function update(Request $request, Coupon $coupon)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param \App\Models\Coupon $coupon
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function destroy(Coupon $coupon)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function redeem(Request $request)
|
||||
{
|
||||
return $this->validateCoupon($request);
|
||||
}
|
||||
}
|
|
@ -9,6 +9,8 @@ use App\Models\PartnerDiscount;
|
|||
use App\Models\Payment;
|
||||
use App\Models\User;
|
||||
use App\Models\ShopProduct;
|
||||
use App\Models\Coupon;
|
||||
use App\Traits\Coupon as CouponTrait;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
|
@ -20,11 +22,16 @@ use Illuminate\Support\Facades\Auth;
|
|||
use App\Helpers\ExtensionHelper;
|
||||
use App\Settings\GeneralSettings;
|
||||
use App\Settings\LocaleSettings;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PaymentController extends Controller
|
||||
{
|
||||
const BUY_PERMISSION = 'user.shop.buy';
|
||||
const VIEW_PERMISSION = "admin.payments.read";
|
||||
|
||||
use CouponTrait;
|
||||
|
||||
/**
|
||||
* @return Application|Factory|View
|
||||
*/
|
||||
|
@ -123,6 +130,8 @@ class PaymentController extends Controller
|
|||
{
|
||||
$product = ShopProduct::find($request->product_id);
|
||||
$paymentGateway = $request->payment_method;
|
||||
$coupon_data = null;
|
||||
$coupon_code = $request->coupon_code;
|
||||
|
||||
// on free products, we don't need to use a payment gateway
|
||||
$realPrice = $product->price - ($product->price * PartnerDiscount::getDiscount() / 100);
|
||||
|
@ -130,6 +139,22 @@ class PaymentController extends Controller
|
|||
return $this->handleFreeProduct($product);
|
||||
}
|
||||
|
||||
if ($coupon_code) {
|
||||
$isValidCoupon = $this->validateCoupon($request);
|
||||
|
||||
if ($isValidCoupon->getStatusCode() == 200) {
|
||||
$coupon_data = $isValidCoupon;
|
||||
$discountPrice = $this->calcDiscount($product, $isValidCoupon->getData());
|
||||
}
|
||||
}
|
||||
|
||||
if ($coupon_data) {
|
||||
return redirect()->route('payment.' . $paymentGateway . 'Pay', [
|
||||
'shopProduct' => $product->id,
|
||||
'discountPrice' => $discountPrice
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('payment.' . $paymentGateway . 'Pay', ['shopProduct' => $product->id]);
|
||||
}
|
||||
|
||||
|
@ -141,6 +166,11 @@ class PaymentController extends Controller
|
|||
return redirect()->route('store.index')->with('info', 'Payment was Canceled');
|
||||
}
|
||||
|
||||
protected function getCouponDiscount(float $productPrice, string $discount)
|
||||
{
|
||||
return $productPrice - ($productPrice * $discount / 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return JsonResponse|mixed
|
||||
*
|
||||
|
|
|
@ -34,8 +34,7 @@ class ProfileController extends Controller
|
|||
'force_discord_verification' => $user_settings->force_discord_verification,
|
||||
'discord_client_id' => $discord_settings->client_id,
|
||||
'discord_client_secret' => $discord_settings->client_secret,
|
||||
'referral_enabled' => $referral_settings->enabled,
|
||||
'referral_allowed' => $referral_settings->allowed
|
||||
'referral_enabled' => $referral_settings->enabled
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
162
app/Models/Coupon.php
Normal file
162
app/Models/Coupon.php
Normal file
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class Coupon extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'type',
|
||||
'value',
|
||||
'uses',
|
||||
'max_uses',
|
||||
'expires_at'
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns the date format used by the coupons.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function formatDate(): string
|
||||
{
|
||||
return 'Y-MM-DD HH:mm:ss';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current state of the coupon.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getStatus()
|
||||
{
|
||||
if ($this->uses >= $this->max_uses) {
|
||||
return 'USES_LIMIT_REACHED';
|
||||
}
|
||||
|
||||
if (! is_null($this->expires_at)) {
|
||||
$expires_at = Carbon::createFromTimeString($this->expires_at)->timestamp;
|
||||
|
||||
if ($expires_at <= Carbon::now()->timestamp) {
|
||||
return __('EXPIRED');
|
||||
}
|
||||
}
|
||||
|
||||
return __('VALID');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user has already exceeded the uses of a coupon.
|
||||
*
|
||||
* @param Request $request The request being made.
|
||||
* @param CouponSettings $coupon_settings The instance of the coupon settings.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isLimitsUsesReached($request, $coupon_settings): bool
|
||||
{
|
||||
$coupon_uses = $request->user()->coupons()->where('id', $this->id)->count();
|
||||
|
||||
return $coupon_uses >= $coupon_settings->max_uses_per_user ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a specified quantity of coupon codes.
|
||||
*
|
||||
* @param int $amount Amount of coupons to be generated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function generateRandomCoupon(int $amount = 10): array
|
||||
{
|
||||
$coupons = [];
|
||||
|
||||
for ($i = 0; $i < $amount; $i++) {
|
||||
$random_coupon = strtoupper(bin2hex(random_bytes(3)));
|
||||
|
||||
$coupons[] = $random_coupon;
|
||||
}
|
||||
|
||||
return $coupons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standardize queries into one single function.
|
||||
*
|
||||
* @param string $code Coupon Code.
|
||||
* @param array $attributes Attributes to be returned.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getQueryData(string $code, array $attributes): mixed
|
||||
{
|
||||
$query = (Coupon::where('code', $code)
|
||||
->where('expires_at', '>', Carbon::now())
|
||||
->whereColumn('uses', '<=', 'max_uses')
|
||||
->get($attributes)->toArray()
|
||||
);
|
||||
|
||||
// When there are results, it comes nested arrays, idk why. This is the solution for now.
|
||||
$results = count($query) > 0 ? $query[0] : $query;
|
||||
|
||||
if (empty($results)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data from a coupon.
|
||||
*
|
||||
* @param string $code Coupon Code.
|
||||
* @param array $attributes Attributes of a coupon.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCoupon(string $code, array $attributes = ['percentage']): mixed
|
||||
{
|
||||
$coupon = $this->getQueryData($code, $attributes);
|
||||
|
||||
if (is_null($coupon)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $coupon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the use of a coupon.
|
||||
*
|
||||
* @param string $code Coupon Code.
|
||||
* @param int $amount Amount to increment.
|
||||
*
|
||||
* @return null|bool
|
||||
*/
|
||||
public function incrementUses(string $code, int $amount = 1): null|bool
|
||||
{
|
||||
$coupon = $this->getQueryData($code, ['uses', 'max_uses']);
|
||||
|
||||
if (empty($coupon) || $coupon['uses'] == $coupon['max_uses']) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->where('code', $code)->increment('uses', $amount);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'user_coupons');
|
||||
}
|
||||
}
|
|
@ -4,8 +4,6 @@ namespace App\Models;
|
|||
|
||||
use App\Notifications\Auth\QueuedVerifyEmail;
|
||||
use App\Notifications\WelcomeMessage;
|
||||
use App\Settings\GeneralSettings;
|
||||
use App\Settings\UserSettings;
|
||||
use App\Classes\PterodactylClient;
|
||||
use App\Settings\PterodactylSettings;
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
|
@ -170,6 +168,14 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
return $this->belongsToMany(Voucher::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsToMany
|
||||
*/
|
||||
public function coupons()
|
||||
{
|
||||
return $this->belongsToMany(Coupon::class, 'user_coupons');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasOne
|
||||
*/
|
||||
|
|
43
app/Settings/CouponSettings.php
Normal file
43
app/Settings/CouponSettings.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Settings;
|
||||
|
||||
use Spatie\LaravelSettings\Settings;
|
||||
|
||||
class CouponSettings extends Settings
|
||||
{
|
||||
public ?int $max_uses_per_user;
|
||||
|
||||
public static function group(): string
|
||||
{
|
||||
return 'coupon';
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary of validations array
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function getValidations()
|
||||
{
|
||||
return [
|
||||
'max_uses_per_user' => 'required|integer'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary of optionInputData array
|
||||
* Only used for the settings page
|
||||
* @return array<array<'type'|'label'|'description'|'options', string|bool|float|int|array<string, string>>>
|
||||
*/
|
||||
public static function getOptionInputData()
|
||||
{
|
||||
return [
|
||||
"category_icon" => "fas fa-ticket-alt",
|
||||
'max_uses_per_user' => [
|
||||
'label' => 'Max Uses Per User',
|
||||
'type' => 'number',
|
||||
'description' => 'Maximum number of uses that a user can make of the same coupon.',
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
86
app/Traits/Coupon.php
Normal file
86
app/Traits/Coupon.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Settings\CouponSettings;
|
||||
use App\Models\Coupon as CouponModel;
|
||||
use App\Models\ShopProduct;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use stdClass;
|
||||
|
||||
trait Coupon
|
||||
{
|
||||
public function validateCoupon(Request $request): JsonResponse
|
||||
{
|
||||
$coupon = CouponModel::where('code', $request->input('coupon_code'))->first();
|
||||
$coupon_settings = new CouponSettings;
|
||||
$response = response()->json([
|
||||
'isValid' => false,
|
||||
'error' => __('This coupon does not exist.')
|
||||
], 404);
|
||||
|
||||
if (!is_null($coupon)) {
|
||||
if ($coupon->getStatus() == 'USES_LIMIT_REACHED') {
|
||||
$response = response()->json([
|
||||
'isValid' => false,
|
||||
'error' => __('This coupon has reached the maximum amount of uses.')
|
||||
], 422);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($coupon->getStatus() == 'EXPIRED') {
|
||||
$response = response()->json([
|
||||
'isValid' => false,
|
||||
'error' => __('This coupon has expired.')
|
||||
], 422);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ($coupon->isLimitsUsesReached($request, $coupon_settings)) {
|
||||
$response = response()->json([
|
||||
'isValid' => false,
|
||||
'error' => __('You have reached the maximum uses of this coupon.')
|
||||
], 422);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response = response()->json([
|
||||
'isValid' => true,
|
||||
'couponCode' => $coupon->code,
|
||||
'couponType' => $coupon->type,
|
||||
'couponValue' => $coupon->value
|
||||
]);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function calcDiscount(ShopProduct $product, stdClass $data)
|
||||
{
|
||||
|
||||
if ($data->isValid) {
|
||||
$productPrice = $product->price;
|
||||
|
||||
if (is_string($productPrice)) {
|
||||
$productPrice = floatval($product->price);
|
||||
}
|
||||
|
||||
if ($data->couponType === 'percentage') {
|
||||
return $productPrice - ($productPrice * $data->couponValue / 100);
|
||||
}
|
||||
|
||||
if ($data->couponType === 'amount') {
|
||||
// There is no discount if the value of the coupon is greater than or equal to the value of the product.
|
||||
if ($data->couponValue >= $productPrice) {
|
||||
return $productPrice;
|
||||
}
|
||||
}
|
||||
|
||||
return $productPrice - $data->couponValue;
|
||||
}
|
||||
}
|
||||
}
|
0
bootstrap/cache/.gitignore
vendored
Normal file → Executable file
0
bootstrap/cache/.gitignore
vendored
Normal file → Executable file
|
@ -74,6 +74,9 @@ return [
|
|||
'admin.partners.read',
|
||||
'admin.partners.write',
|
||||
|
||||
'admin.coupons.read',
|
||||
'admin.coupons.write',
|
||||
|
||||
'admin.logs.read',
|
||||
|
||||
/*
|
||||
|
|
|
@ -12,6 +12,7 @@ use App\Settings\ServerSettings;
|
|||
use App\Settings\UserSettings;
|
||||
use App\Settings\WebsiteSettings;
|
||||
use App\Settings\TicketSettings;
|
||||
use App\Settings\CouponSettings;
|
||||
|
||||
return [
|
||||
|
||||
|
@ -31,6 +32,7 @@ return [
|
|||
UserSettings::class,
|
||||
WebsiteSettings::class,
|
||||
TicketSettings::class,
|
||||
CouponSettings::class,
|
||||
],
|
||||
|
||||
/*
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('coupons', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('code')->unique();
|
||||
$table->enum('type', ['percentage', 'amount']);
|
||||
$table->integer('value');
|
||||
$table->integer('uses')->default(0);
|
||||
$table->integer('max_uses');
|
||||
$table->timestamp('expires_at');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('coupons');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('user_coupons', function (Blueprint $table) {
|
||||
$table->foreignId('user_id')->constrained();
|
||||
$table->foreignId('coupon_id')->constrained();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('user_coupons');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
use Spatie\LaravelSettings\Migrations\SettingsMigration;
|
||||
|
||||
return new class extends SettingsMigration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
$this->migrator->add('coupon.max_uses_per_user', 1);
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
$this->migrator->delete('coupon.max_uses_per_user');
|
||||
}
|
||||
};
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "controllpanelgg",
|
||||
"name": "controlpanel",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
|
|
@ -22,6 +22,7 @@ use App\Http\Controllers\Admin\TicketsController as AdminTicketsController;
|
|||
use App\Http\Controllers\Admin\UsefulLinkController;
|
||||
use App\Http\Controllers\Admin\UserController;
|
||||
use App\Http\Controllers\Admin\VoucherController;
|
||||
use App\Http\Controllers\Admin\CouponController;
|
||||
use App\Http\Controllers\Auth\SocialiteController;
|
||||
use App\Http\Controllers\HomeController;
|
||||
use App\Http\Controllers\NotificationController;
|
||||
|
@ -199,6 +200,10 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
|
|||
Route::get('partners/{voucher}/users', [PartnerController::class, 'users'])->name('partners.users');
|
||||
Route::resource('partners', PartnerController::class);
|
||||
|
||||
//coupons
|
||||
Route::post('coupons/redeem', [CouponController::class, 'redeem'])->name('coupon.redeem');
|
||||
Route::resource('coupons', CouponController::class);
|
||||
|
||||
//api-keys
|
||||
Route::get('api/datatable', [ApplicationApiController::class, 'datatable'])->name('api.datatable');
|
||||
Route::resource('api', ApplicationApiController::class)->parameters([
|
||||
|
|
0
storage/app/.gitignore
vendored
Normal file → Executable file
0
storage/app/.gitignore
vendored
Normal file → Executable file
0
storage/app/public/.gitignore
vendored
Normal file → Executable file
0
storage/app/public/.gitignore
vendored
Normal file → Executable file
0
storage/debugbar/.gitignore
vendored
Normal file → Executable file
0
storage/debugbar/.gitignore
vendored
Normal file → Executable file
0
storage/framework/.gitignore
vendored
Normal file → Executable file
0
storage/framework/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/.gitignore
vendored
Normal file → Executable file
0
storage/framework/cache/.gitignore
vendored
Normal file → Executable file
0
storage/framework/sessions/.gitignore
vendored
Normal file → Executable file
0
storage/framework/sessions/.gitignore
vendored
Normal file → Executable file
0
storage/framework/testing/.gitignore
vendored
Normal file → Executable file
0
storage/framework/testing/.gitignore
vendored
Normal file → Executable file
0
storage/framework/views/.gitignore
vendored
Normal file → Executable file
0
storage/framework/views/.gitignore
vendored
Normal file → Executable file
0
storage/logs/.gitignore
vendored
Normal file → Executable file
0
storage/logs/.gitignore
vendored
Normal file → Executable file
289
themes/default/views/admin/coupons/create.blade.php
Normal file
289
themes/default/views/admin/coupons/create.blade.php
Normal file
|
@ -0,0 +1,289 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<!-- CONTENT HEADER -->
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1>{{__('Coupon')}}</h1>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb float-sm-right">
|
||||
<li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{route('admin.coupons.index')}}">{{__('Coupons')}}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item"><a class="text-muted"
|
||||
href="{{route('admin.coupons.create')}}">{{__('Create')}}</a>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT HEADER -->
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">
|
||||
<i class="nav-icon fas fa-ticket-alt"></i>
|
||||
{{__('Coupon Details')}}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="{{ route('admin.coupons.store') }}" method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<div class="custom-control custom-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="random_codes"
|
||||
name="random_codes"
|
||||
class="custom-control-input"
|
||||
>
|
||||
<label for="random_codes" class="custom-control-label">
|
||||
{{ __('Random Codes') }}
|
||||
<i
|
||||
data-toggle="popover"
|
||||
data-trigger="hover"
|
||||
data-content="{{__('Replace the creation of a single code with several at once with a custom field.')}}"
|
||||
class="fas fa-info-circle">
|
||||
</i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="range_codes_element" style="display: none;" class="form-group">
|
||||
<label for="range_codes">
|
||||
{{ __('Range Codes') }}
|
||||
<i
|
||||
data-toggle="popover"
|
||||
data-trigger="hover"
|
||||
data-content="{{__('Generate a number of random codes.')}}"
|
||||
class="fas fa-info-circle">
|
||||
</i>
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="range_codes"
|
||||
name="range_codes"
|
||||
step="any"
|
||||
min="1"
|
||||
max="100"
|
||||
class="form-control @error('range_codes') is-invalid @enderror"
|
||||
>
|
||||
@error('range_codes')
|
||||
<div class="text-danger">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div id="coupon_code_element" class="form-group">
|
||||
<label for="coupon_code">
|
||||
{{ __('Coupon Code') }}
|
||||
<i
|
||||
data-toggle="popover"
|
||||
data-trigger="hover"
|
||||
data-content="{{__('The coupon code to be registered.')}}"
|
||||
class="fas fa-info-circle">
|
||||
</i>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="coupon_code"
|
||||
name="coupon_code"
|
||||
placeholder="SUMMER"
|
||||
class="form-control @error('coupon_code') is-invalid @enderror"
|
||||
value="{{ old('coupon_code') }}"
|
||||
>
|
||||
@error('coupon_code')
|
||||
<div class="text-danger">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="custom-control mb-3 p-0">
|
||||
<label for="coupon_type">
|
||||
{{ __('Coupon Type') }}
|
||||
<i
|
||||
data-toggle="popover"
|
||||
data-trigger="hover"
|
||||
data-content="{{__('The way the coupon should discount.')}}"
|
||||
class="fas fa-info-circle">
|
||||
</i>
|
||||
</label>
|
||||
<select
|
||||
name="coupon_type"
|
||||
id="coupon_type"
|
||||
class="custom-select @error('coupon_type') is_invalid @enderror"
|
||||
style="width: 100%; cursor: pointer;"
|
||||
autocomplete="off"
|
||||
required
|
||||
>
|
||||
<option value="percentage" @if(old('coupon_type') == 'percentage') selected @endif>{{ __('Percentage') }}</option>
|
||||
<option value="amount" @if(old('coupon_type') == 'amount') selected @endif>{{ __('Amount') }}</option>
|
||||
</select>
|
||||
@error('coupon_type')
|
||||
<div class="text-danger">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="input-group d-flex flex-column">
|
||||
<label for="coupon_value">
|
||||
{{ __('Coupon Value') }}
|
||||
<i
|
||||
data-toggle="popover"
|
||||
data-trigger="hover"
|
||||
data-content="{{__('The value that the coupon will represent.')}}"
|
||||
class="fas fa-info-circle">
|
||||
</i>
|
||||
</label>
|
||||
<div class="d-flex">
|
||||
<input
|
||||
name="coupon_value"
|
||||
id="coupon_value"
|
||||
type="number"
|
||||
step="any"
|
||||
min="1"
|
||||
max="100"
|
||||
class="form-control @error('coupon_value') is-invalid @enderror"
|
||||
value="{{ old('coupon_value') }}"
|
||||
>
|
||||
<span id="input_percentage" class="input-group-text">%</span>
|
||||
</div>
|
||||
@error('coupon_value')
|
||||
<div class="text-danger">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="coupon_uses">
|
||||
{{ __('Max uses') }}
|
||||
<i
|
||||
data-toggle="popover"
|
||||
data-trigger="hover"
|
||||
data-content="{{__('The maximum number of times the coupon can be used.')}}"
|
||||
class="fas fa-info-circle">
|
||||
</i>
|
||||
</label>
|
||||
<input
|
||||
name="coupon_uses"
|
||||
id="coupon_uses"
|
||||
type="number"
|
||||
step="any"
|
||||
min="1"
|
||||
max="100"
|
||||
class="form-control @error('coupon_uses') is-invalid @enderror"
|
||||
value="{{ old('coupon_uses') }}"
|
||||
>
|
||||
@error('coupon_uses')
|
||||
<div class="text-danger">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="d-flex flex-column input-group form-group date" id="datepicker" data-target-input="nearest">
|
||||
<label for="datepicker">
|
||||
{{ __('Expires at') }}
|
||||
<i
|
||||
data-toggle="popover"
|
||||
data-trigger="hover"
|
||||
data-content="{{__('The date when the coupon will expire.')}}"
|
||||
class="fas fa-info-circle">
|
||||
</i>
|
||||
</label>
|
||||
<div class="d-flex">
|
||||
<input
|
||||
value="{{old('datepicker')}}"
|
||||
name="datepicker"
|
||||
placeholder="yyyy-mm-dd hh:mm:ss"
|
||||
type="text"
|
||||
class="form-control @error('datepicker') is-invalid @enderror datetimepicker-input"
|
||||
data-target="#datepicker"
|
||||
/>
|
||||
<div
|
||||
class="input-group-append"
|
||||
data-target="#datepicker"
|
||||
data-toggle="datetimepicker"
|
||||
>
|
||||
<div class="input-group-text">
|
||||
<i class="fa fa-calendar"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@error('datepicker')
|
||||
<div class="text-danger">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
<div class="form-group text-right mb-0">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
{{__('Submit')}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT -->
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#datepicker').datetimepicker({
|
||||
format: 'Y-MM-DD HH:mm:ss',
|
||||
icons: {
|
||||
time: 'far fa-clock',
|
||||
date: 'far fa-calendar',
|
||||
up: 'fas fa-arrow-up',
|
||||
down: 'fas fa-arrow-down',
|
||||
previous: 'fas fa-chevron-left',
|
||||
next: 'fas fa-chevron-right',
|
||||
today: 'fas fa-calendar-check',
|
||||
clear: 'far fa-trash-alt',
|
||||
close: 'far fa-times-circle'
|
||||
}
|
||||
});
|
||||
$('#random_codes').change(function() {
|
||||
if ($(this).is(':checked')) {
|
||||
$('#coupon_code_element').prop('disabled', true).hide()
|
||||
$('#range_codes_element').prop('disabled', false).show()
|
||||
|
||||
if ($('#coupon_code').val()) {
|
||||
$('#coupon_code').prop('value', null)
|
||||
}
|
||||
|
||||
} else {
|
||||
$('#coupon_code_element').prop('disabled', false).show()
|
||||
$('#range_codes_element').prop('disabled', true).hide()
|
||||
|
||||
if ($('#range_codes').val()) {
|
||||
$('#range_codes').prop('value', null)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
$('#coupon_type').change(function() {
|
||||
if ($(this).val() == 'percentage') {
|
||||
$('#input_percentage').prop('disabled', false).show()
|
||||
} else {
|
||||
$('#input_percentage').prop('disabled', true).hide()
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@endsection
|
65
themes/default/views/admin/coupons/index.blade.php
Normal file
65
themes/default/views/admin/coupons/index.blade.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<!-- CONTENT HEADER -->
|
||||
<section class="content-header">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-2">
|
||||
<div class="col-sm-6">
|
||||
<h1>{{__('Coupons')}}</h1>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<ol class="breadcrumb float-sm-right">
|
||||
<li class="breadcrumb-item"><a href="{{route('home')}}">{{__('Dashboard')}}</a></li>
|
||||
<li class="breadcrumb-item"><a class="text-muted"
|
||||
href="{{route('admin.coupons.index')}}">{{__('Coupons')}}</a></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- END CONTENT HEADER -->
|
||||
|
||||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between">
|
||||
<h5 class="card-title">
|
||||
<i class="nav-icon fas fa-ticket-alt"></i>
|
||||
{{__('Coupons')}}
|
||||
</h5>
|
||||
<a href="{{route('admin.coupons.create')}}" class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-plus mr-1"></i>
|
||||
{{__('Create new')}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body table-responsive">
|
||||
|
||||
<table id="datatable" class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{__('Partner discount')}}</th>
|
||||
<th>{{__('Registered user discount')}}</th>
|
||||
<th>{{__('Referral system commission')}}</th>
|
||||
<th>{{__('Created')}}</th>
|
||||
<th>{{__('Actions')}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!-- END CUSTOM CONTENT -->
|
||||
|
||||
</section>
|
||||
<!-- END CONTENT -->
|
||||
@endsection
|
|
@ -424,7 +424,7 @@
|
|||
</a>
|
||||
</li>
|
||||
@endcanany
|
||||
@canany(["admin.voucher.read","admin.voucher.read"])
|
||||
@canany(["admin.voucher.read","admin.voucher.write"])
|
||||
<li class="nav-item">
|
||||
<a href="{{ route('admin.vouchers.index') }}"
|
||||
class="nav-link @if (Request::routeIs('admin.vouchers.*')) active @endif">
|
||||
|
@ -433,7 +433,7 @@
|
|||
</a>
|
||||
</li>
|
||||
@endcanany
|
||||
@canany(["admin.partners.read","admin.partners.read"])
|
||||
@canany(["admin.partners.read","admin.partners.write"])
|
||||
<li class="nav-item">
|
||||
<a href="{{ route('admin.partners.index') }}"
|
||||
class="nav-link @if (Request::routeIs('admin.partners.*')) active @endif">
|
||||
|
@ -443,6 +443,16 @@
|
|||
</li>
|
||||
@endcanany
|
||||
|
||||
@canany(["admin.coupons.read", "admin.coupons.write"])
|
||||
<li class="nav-item">
|
||||
<a href="{{ route('admin.coupons.index') }}"
|
||||
class="nav-link @if (Request::routeIs('admin.coupons.*')) active @endif">
|
||||
<i class="nav-icon fas fa-ticket-alt"></i>
|
||||
<p>{{ __('Coupons') }}</p>
|
||||
</a>
|
||||
</li>
|
||||
@endcanany
|
||||
|
||||
@canany(["admin.useful_links.read","admin.legal.read"])
|
||||
<li class="nav-header">{{ __('Other') }}</li>
|
||||
@endcanany
|
||||
|
|
|
@ -24,7 +24,19 @@
|
|||
<!-- MAIN CONTENT -->
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
<form x-data="{ payment_method: '', clicked: false }" action="{{ route('payment.pay') }}" method="POST">
|
||||
<form
|
||||
id="payment_form"
|
||||
action="{{ route('payment.pay') }}"
|
||||
method="POST"
|
||||
x-data="{
|
||||
payment_method: '',
|
||||
coupon_code: '',
|
||||
clicked: false,
|
||||
setCouponCode(event) {
|
||||
this.coupon_code = event.target.value
|
||||
}
|
||||
}"
|
||||
>
|
||||
@csrf
|
||||
@method('post')
|
||||
<div class="row d-flex justify-content-center flex-wrap">
|
||||
|
@ -67,6 +79,45 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="mb-0">
|
||||
Coupon Code
|
||||
</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex">
|
||||
<input
|
||||
type="text"
|
||||
id="coupon_code"
|
||||
name="coupon_code"
|
||||
value="{{ old('coupon_code') }}"
|
||||
:value="coupon_code"
|
||||
class="form-control @error('coupon_code') is_invalid @enderror"
|
||||
placeholder="SUMMER"
|
||||
x-on:change.debounce="setCouponCode($event)"
|
||||
x-model="coupon_code"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
id="send_coupon_code"
|
||||
class="btn btn-success ml-3"
|
||||
:disabled="!coupon_code.length"
|
||||
:class="!coupon_code.length ? 'disabled' : ''"
|
||||
:value="coupon_code"
|
||||
>
|
||||
{{ __('Submit') }}
|
||||
</button>
|
||||
</div>
|
||||
@error('coupon_code')
|
||||
<div class="text-danger">
|
||||
{{ $message }}
|
||||
</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="col-xl-3">
|
||||
<div class="card">
|
||||
|
@ -131,6 +182,12 @@
|
|||
<span class="text-muted d-inline-block">
|
||||
+ {{ $product->formatToCurrency($taxvalue) }}</span>
|
||||
</div>
|
||||
<div id="coupon_discount_details" class="d-flex justify-content-between" style="display: none !important;">
|
||||
<span class="text-muted d-inline-block">
|
||||
{{ __('Coupon Discount') }}
|
||||
|
||||
</span>
|
||||
</div>
|
||||
@if ($discountpercent && $discountvalue)
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-muted d-inline-block">{{ __('Discount') }}
|
||||
|
@ -155,9 +212,12 @@
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<button :disabled="(!payment_method || !clicked) && {{ !$productIsFree }}"
|
||||
:class="(!payment_method || !clicked) && {{ !$productIsFree }} ? 'disabled' : ''"
|
||||
<button :disabled="(!payment_method || !clicked || coupon_code ? true : false) && {{ !$productIsFree }}"
|
||||
id="submit_form_button"
|
||||
:class="(!payment_method || !clicked || coupon_code ? true : false) && {{ !$productIsFree }} ? 'disabled' : ''"
|
||||
:x-text="coupon_code"
|
||||
class="btn btn-success float-right w-100">
|
||||
|
||||
<i class="far fa-credit-card mr-2" @click="clicked == true"></i>
|
||||
@if ($productIsFree)
|
||||
{{ __('Get for free') }}
|
||||
|
@ -166,6 +226,8 @@
|
|||
@endif
|
||||
|
||||
</button>
|
||||
<script>
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -175,4 +237,64 @@
|
|||
|
||||
</section>
|
||||
<!-- END CONTENT -->
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
let hasCouponCodeValue = $('#coupon_code').val().trim() !== ''
|
||||
|
||||
$('#coupon_code').on('change', function(e) {
|
||||
hasCouponCodeValue = e.target.value !== ''
|
||||
})
|
||||
|
||||
function checkCoupon() {
|
||||
const couponCode = $('#coupon_code').val()
|
||||
|
||||
$.ajax({
|
||||
url: "{{ route('admin.coupon.redeem') }}",
|
||||
method: 'POST',
|
||||
data: { coupon_code: couponCode },
|
||||
success: function(response) {
|
||||
if (response.isValid && response.couponCode) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
text: `The coupon '${response.couponCode}' was successfully inserted in your purchase.`,
|
||||
}).then(function(isConfirmed) {
|
||||
console.log('confirmou')
|
||||
|
||||
$('#submit_form_button').prop('disabled', false).removeClass('disabled')
|
||||
$('#send_coupon_code').prop('disabled', true)
|
||||
$('#coupon_discount_details').prop('disabled', false).show()
|
||||
})
|
||||
|
||||
} else {
|
||||
console.log('Invalid Coupon')
|
||||
}
|
||||
},
|
||||
error: function(response) {
|
||||
const responseJson = response.responseJSON
|
||||
|
||||
if (!responseJson.isValid) {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Oops...',
|
||||
text: responseJson.error,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$('#payment_form').on('submit', function(e) {
|
||||
if (hasCouponCodeValue) {
|
||||
checkCoupon()
|
||||
}
|
||||
})
|
||||
|
||||
$('#send_coupon_code').click(function(e) {
|
||||
if (hasCouponCodeValue) {
|
||||
checkCoupon()
|
||||
}
|
||||
})
|
||||
})
|
||||
</script>
|
||||
@endsection
|
||||
|
|
Loading…
Reference in a new issue