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
|
charset = utf-8
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 4
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
|
@ -13,3 +13,24 @@ trim_trailing_whitespace = false
|
||||||
|
|
||||||
[*.{yml,yaml}]
|
[*.{yml,yaml}]
|
||||||
indent_size = 2
|
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
|
.env.backup
|
||||||
.idea
|
.idea
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
|
.editorconfig
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
Homestead.json
|
Homestead.json
|
||||||
Homestead.yaml
|
Homestead.yaml
|
||||||
|
|
|
@ -10,6 +10,7 @@ use App\Models\PartnerDiscount;
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
use App\Models\ShopProduct;
|
use App\Models\ShopProduct;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use App\Models\Coupon;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Redirect;
|
use Illuminate\Support\Facades\Redirect;
|
||||||
|
@ -41,6 +42,17 @@ class PayPalExtension extends AbstractExtension
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
$shopProduct = ShopProduct::findOrFail($request->shopProduct);
|
$shopProduct = ShopProduct::findOrFail($request->shopProduct);
|
||||||
$discount = PartnerDiscount::getDiscount();
|
$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
|
// create a new payment
|
||||||
$payment = Payment::create([
|
$payment = Payment::create([
|
||||||
|
@ -50,7 +62,7 @@ class PayPalExtension extends AbstractExtension
|
||||||
'type' => $shopProduct->type,
|
'type' => $shopProduct->type,
|
||||||
'status' => 'open',
|
'status' => 'open',
|
||||||
'amount' => $shopProduct->quantity,
|
'amount' => $shopProduct->quantity,
|
||||||
'price' => $shopProduct->price - ($shopProduct->price * $discount / 100),
|
'price' => $price,
|
||||||
'tax_value' => $shopProduct->getTaxValue(),
|
'tax_value' => $shopProduct->getTaxValue(),
|
||||||
'tax_percent' => $shopProduct->getTaxPercent(),
|
'tax_percent' => $shopProduct->getTaxPercent(),
|
||||||
'total_price' => $shopProduct->getTotalPrice(),
|
'total_price' => $shopProduct->getTotalPrice(),
|
||||||
|
@ -73,7 +85,7 @@ class PayPalExtension extends AbstractExtension
|
||||||
'item_total' =>
|
'item_total' =>
|
||||||
[
|
[
|
||||||
'currency_code' => strtoupper($shopProduct->currency_code),
|
'currency_code' => strtoupper($shopProduct->currency_code),
|
||||||
'value' => $shopProduct->getPriceAfterDiscount(),
|
'value' => number_format($price, 2),
|
||||||
],
|
],
|
||||||
'tax_total' =>
|
'tax_total' =>
|
||||||
[
|
[
|
||||||
|
@ -86,7 +98,7 @@ class PayPalExtension extends AbstractExtension
|
||||||
],
|
],
|
||||||
"application_context" => [
|
"application_context" => [
|
||||||
"cancel_url" => route('payment.Cancel'),
|
"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'),
|
'brand_name' => config('app.name', 'CtrlPanel.GG'),
|
||||||
'shipping_preference' => 'NO_SHIPPING'
|
'shipping_preference' => 'NO_SHIPPING'
|
||||||
]
|
]
|
||||||
|
@ -126,6 +138,7 @@ class PayPalExtension extends AbstractExtension
|
||||||
|
|
||||||
$payment = Payment::findOrFail($laravelRequest->payment);
|
$payment = Payment::findOrFail($laravelRequest->payment);
|
||||||
$shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
|
$shopProduct = ShopProduct::findOrFail($payment->shop_item_product_id);
|
||||||
|
$coupon_code = $laravelRequest->input('couponCode');
|
||||||
|
|
||||||
$request = new OrdersCaptureRequest($laravelRequest->input('token'));
|
$request = new OrdersCaptureRequest($laravelRequest->input('token'));
|
||||||
$request->prefer('return=representation');
|
$request->prefer('return=representation');
|
||||||
|
@ -140,6 +153,12 @@ class PayPalExtension extends AbstractExtension
|
||||||
'payment_id' => $response->result->id,
|
'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 UserUpdateCreditsEvent($user));
|
||||||
event(new PaymentEvent($user, $payment, $shopProduct));
|
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\Payment;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\ShopProduct;
|
use App\Models\ShopProduct;
|
||||||
|
use App\Models\Coupon;
|
||||||
|
use App\Traits\Coupon as CouponTrait;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
use Illuminate\Contracts\View\Factory;
|
use Illuminate\Contracts\View\Factory;
|
||||||
|
@ -20,11 +22,16 @@ use Illuminate\Support\Facades\Auth;
|
||||||
use App\Helpers\ExtensionHelper;
|
use App\Helpers\ExtensionHelper;
|
||||||
use App\Settings\GeneralSettings;
|
use App\Settings\GeneralSettings;
|
||||||
use App\Settings\LocaleSettings;
|
use App\Settings\LocaleSettings;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class PaymentController extends Controller
|
class PaymentController extends Controller
|
||||||
{
|
{
|
||||||
const BUY_PERMISSION = 'user.shop.buy';
|
const BUY_PERMISSION = 'user.shop.buy';
|
||||||
const VIEW_PERMISSION = "admin.payments.read";
|
const VIEW_PERMISSION = "admin.payments.read";
|
||||||
|
|
||||||
|
use CouponTrait;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Application|Factory|View
|
* @return Application|Factory|View
|
||||||
*/
|
*/
|
||||||
|
@ -123,6 +130,8 @@ class PaymentController extends Controller
|
||||||
{
|
{
|
||||||
$product = ShopProduct::find($request->product_id);
|
$product = ShopProduct::find($request->product_id);
|
||||||
$paymentGateway = $request->payment_method;
|
$paymentGateway = $request->payment_method;
|
||||||
|
$coupon_data = null;
|
||||||
|
$coupon_code = $request->coupon_code;
|
||||||
|
|
||||||
// on free products, we don't need to use a payment gateway
|
// on free products, we don't need to use a payment gateway
|
||||||
$realPrice = $product->price - ($product->price * PartnerDiscount::getDiscount() / 100);
|
$realPrice = $product->price - ($product->price * PartnerDiscount::getDiscount() / 100);
|
||||||
|
@ -130,6 +139,22 @@ class PaymentController extends Controller
|
||||||
return $this->handleFreeProduct($product);
|
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]);
|
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');
|
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
|
* @return JsonResponse|mixed
|
||||||
*
|
*
|
||||||
|
|
|
@ -34,8 +34,7 @@ class ProfileController extends Controller
|
||||||
'force_discord_verification' => $user_settings->force_discord_verification,
|
'force_discord_verification' => $user_settings->force_discord_verification,
|
||||||
'discord_client_id' => $discord_settings->client_id,
|
'discord_client_id' => $discord_settings->client_id,
|
||||||
'discord_client_secret' => $discord_settings->client_secret,
|
'discord_client_secret' => $discord_settings->client_secret,
|
||||||
'referral_enabled' => $referral_settings->enabled,
|
'referral_enabled' => $referral_settings->enabled
|
||||||
'referral_allowed' => $referral_settings->allowed
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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\Auth\QueuedVerifyEmail;
|
||||||
use App\Notifications\WelcomeMessage;
|
use App\Notifications\WelcomeMessage;
|
||||||
use App\Settings\GeneralSettings;
|
|
||||||
use App\Settings\UserSettings;
|
|
||||||
use App\Classes\PterodactylClient;
|
use App\Classes\PterodactylClient;
|
||||||
use App\Settings\PterodactylSettings;
|
use App\Settings\PterodactylSettings;
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
@ -170,6 +168,14 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||||
return $this->belongsToMany(Voucher::class);
|
return $this->belongsToMany(Voucher::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BelongsToMany
|
||||||
|
*/
|
||||||
|
public function coupons()
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Coupon::class, 'user_coupons');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return HasOne
|
* @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.read',
|
||||||
'admin.partners.write',
|
'admin.partners.write',
|
||||||
|
|
||||||
|
'admin.coupons.read',
|
||||||
|
'admin.coupons.write',
|
||||||
|
|
||||||
'admin.logs.read',
|
'admin.logs.read',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -12,6 +12,7 @@ use App\Settings\ServerSettings;
|
||||||
use App\Settings\UserSettings;
|
use App\Settings\UserSettings;
|
||||||
use App\Settings\WebsiteSettings;
|
use App\Settings\WebsiteSettings;
|
||||||
use App\Settings\TicketSettings;
|
use App\Settings\TicketSettings;
|
||||||
|
use App\Settings\CouponSettings;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ return [
|
||||||
UserSettings::class,
|
UserSettings::class,
|
||||||
WebsiteSettings::class,
|
WebsiteSettings::class,
|
||||||
TicketSettings::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,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|
|
@ -22,6 +22,7 @@ use App\Http\Controllers\Admin\TicketsController as AdminTicketsController;
|
||||||
use App\Http\Controllers\Admin\UsefulLinkController;
|
use App\Http\Controllers\Admin\UsefulLinkController;
|
||||||
use App\Http\Controllers\Admin\UserController;
|
use App\Http\Controllers\Admin\UserController;
|
||||||
use App\Http\Controllers\Admin\VoucherController;
|
use App\Http\Controllers\Admin\VoucherController;
|
||||||
|
use App\Http\Controllers\Admin\CouponController;
|
||||||
use App\Http\Controllers\Auth\SocialiteController;
|
use App\Http\Controllers\Auth\SocialiteController;
|
||||||
use App\Http\Controllers\HomeController;
|
use App\Http\Controllers\HomeController;
|
||||||
use App\Http\Controllers\NotificationController;
|
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::get('partners/{voucher}/users', [PartnerController::class, 'users'])->name('partners.users');
|
||||||
Route::resource('partners', PartnerController::class);
|
Route::resource('partners', PartnerController::class);
|
||||||
|
|
||||||
|
//coupons
|
||||||
|
Route::post('coupons/redeem', [CouponController::class, 'redeem'])->name('coupon.redeem');
|
||||||
|
Route::resource('coupons', CouponController::class);
|
||||||
|
|
||||||
//api-keys
|
//api-keys
|
||||||
Route::get('api/datatable', [ApplicationApiController::class, 'datatable'])->name('api.datatable');
|
Route::get('api/datatable', [ApplicationApiController::class, 'datatable'])->name('api.datatable');
|
||||||
Route::resource('api', ApplicationApiController::class)->parameters([
|
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>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@endcanany
|
@endcanany
|
||||||
@canany(["admin.voucher.read","admin.voucher.read"])
|
@canany(["admin.voucher.read","admin.voucher.write"])
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{ route('admin.vouchers.index') }}"
|
<a href="{{ route('admin.vouchers.index') }}"
|
||||||
class="nav-link @if (Request::routeIs('admin.vouchers.*')) active @endif">
|
class="nav-link @if (Request::routeIs('admin.vouchers.*')) active @endif">
|
||||||
|
@ -433,7 +433,7 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@endcanany
|
@endcanany
|
||||||
@canany(["admin.partners.read","admin.partners.read"])
|
@canany(["admin.partners.read","admin.partners.write"])
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{ route('admin.partners.index') }}"
|
<a href="{{ route('admin.partners.index') }}"
|
||||||
class="nav-link @if (Request::routeIs('admin.partners.*')) active @endif">
|
class="nav-link @if (Request::routeIs('admin.partners.*')) active @endif">
|
||||||
|
@ -443,6 +443,16 @@
|
||||||
</li>
|
</li>
|
||||||
@endcanany
|
@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"])
|
@canany(["admin.useful_links.read","admin.legal.read"])
|
||||||
<li class="nav-header">{{ __('Other') }}</li>
|
<li class="nav-header">{{ __('Other') }}</li>
|
||||||
@endcanany
|
@endcanany
|
||||||
|
|
|
@ -24,7 +24,19 @@
|
||||||
<!-- MAIN CONTENT -->
|
<!-- MAIN CONTENT -->
|
||||||
<section class="content">
|
<section class="content">
|
||||||
<div class="container-fluid">
|
<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
|
@csrf
|
||||||
@method('post')
|
@method('post')
|
||||||
<div class="row d-flex justify-content-center flex-wrap">
|
<div class="row d-flex justify-content-center flex-wrap">
|
||||||
|
@ -67,6 +79,45 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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
|
@endif
|
||||||
<div class="col-xl-3">
|
<div class="col-xl-3">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
@ -131,6 +182,12 @@
|
||||||
<span class="text-muted d-inline-block">
|
<span class="text-muted d-inline-block">
|
||||||
+ {{ $product->formatToCurrency($taxvalue) }}</span>
|
+ {{ $product->formatToCurrency($taxvalue) }}</span>
|
||||||
</div>
|
</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)
|
@if ($discountpercent && $discountvalue)
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<span class="text-muted d-inline-block">{{ __('Discount') }}
|
<span class="text-muted d-inline-block">{{ __('Discount') }}
|
||||||
|
@ -155,9 +212,12 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<button :disabled="(!payment_method || !clicked) && {{ !$productIsFree }}"
|
<button :disabled="(!payment_method || !clicked || coupon_code ? true : false) && {{ !$productIsFree }}"
|
||||||
:class="(!payment_method || !clicked) && {{ !$productIsFree }} ? 'disabled' : ''"
|
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">
|
class="btn btn-success float-right w-100">
|
||||||
|
|
||||||
<i class="far fa-credit-card mr-2" @click="clicked == true"></i>
|
<i class="far fa-credit-card mr-2" @click="clicked == true"></i>
|
||||||
@if ($productIsFree)
|
@if ($productIsFree)
|
||||||
{{ __('Get for free') }}
|
{{ __('Get for free') }}
|
||||||
|
@ -166,6 +226,8 @@
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
<script>
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -175,4 +237,64 @@
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
<!-- END CONTENT -->
|
<!-- 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
|
@endsection
|
||||||
|
|
Loading…
Reference in a new issue