diff --git a/app/Console/Commands/DeleteExpiredCoupons.php b/app/Console/Commands/DeleteExpiredCoupons.php new file mode 100644 index 00000000..9d3ded87 --- /dev/null +++ b/app/Console/Commands/DeleteExpiredCoupons.php @@ -0,0 +1,42 @@ +delete_coupon_on_expires) { + $expired_coupons = Coupon::where('expires_at', '<=', Carbon::now(config('app.timezone')))->get(); + + foreach ($expired_coupons as $expired_coupon) { + $expired_coupon->delete(); + } + } + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 64af565f..a1bd37d2 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -16,6 +16,7 @@ class Kernel extends ConsoleKernel protected $commands = [ Commands\ChargeCreditsCommand::class, Commands\ChargeServers::class, + Commands\DeleteExpiredCoupons::class, ]; /** @@ -29,6 +30,7 @@ class Kernel extends ConsoleKernel $schedule->command('servers:charge')->everyMinute(); $schedule->command('cp:versioncheck:get')->daily(); $schedule->command('payments:open:clear')->daily(); + $schedule->command('coupons:delete')->daily(); //log cronjob activity $schedule->call(function () { diff --git a/app/Events/CouponUsedEvent.php b/app/Events/CouponUsedEvent.php new file mode 100644 index 00000000..49168b60 --- /dev/null +++ b/app/Events/CouponUsedEvent.php @@ -0,0 +1,25 @@ +coupon = $coupon; + } +} diff --git a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php index 7667c08d..07771e48 100644 --- a/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php +++ b/app/Extensions/PaymentGateways/PayPal/PayPalExtension.php @@ -2,6 +2,7 @@ namespace App\Extensions\PaymentGateways\PayPal; +use App\Events\CouponUsedEvent; use App\Helpers\AbstractExtension; use App\Events\PaymentEvent; use App\Events\UserUpdateCreditsEvent; @@ -56,6 +57,7 @@ class PayPalExtension extends AbstractExtension // Partner Discount. $price = $price - ($price * $discount / 100); + $price = number_format($price, 2); // create a new payment $payment = Payment::create([ @@ -82,12 +84,12 @@ class PayPalExtension extends AbstractExtension "reference_id" => uniqid(), "description" => $shopProduct->display . ($discount ? (" (" . __('Discount') . " " . $discount . '%)') : ""), "amount" => [ - "value" => $shopProduct->getTotalPrice(), + "value" => $price, 'currency_code' => strtoupper($shopProduct->currency_code), 'breakdown' => [ 'item_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), - 'value' => number_format($price, 2), + 'value' => $price ], 'tax_total' => [ 'currency_code' => strtoupper($shopProduct->currency_code), @@ -158,6 +160,8 @@ class PayPalExtension extends AbstractExtension if ($coupon_code) { $coupon = new Coupon; $coupon->incrementUses($coupon_code); + + event(new CouponUsedEvent($coupon)); } event(new UserUpdateCreditsEvent($user)); diff --git a/app/Http/Controllers/Admin/CouponController.php b/app/Http/Controllers/Admin/CouponController.php index 396e2936..1287a57c 100644 --- a/app/Http/Controllers/Admin/CouponController.php +++ b/app/Http/Controllers/Admin/CouponController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Models\Coupon; +use App\Settings\LocaleSettings; use App\Traits\Coupon as CouponTrait; use Illuminate\Http\Request; use Carbon\Carbon; @@ -20,11 +21,13 @@ class CouponController extends Controller * * @return \Illuminate\Http\Response */ - public function index() + public function index(LocaleSettings $localeSettings) { $this->checkPermission(self::READ_PERMISSION); - return view('admin.coupons.index'); + return view('admin.coupons.index', [ + 'locale_datatables' => $localeSettings->datatables + ]); } /** @@ -47,21 +50,12 @@ class CouponController extends Controller */ 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'); + $coupon_code = $request->input('code'); $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()) - ]; + $rules = $this->requestRules($request); - // If for some reason you pass both fields at once. - if ($coupon_code && $random_codes_amount) { + // 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()); } @@ -69,12 +63,6 @@ class CouponController extends Controller 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)) { @@ -85,23 +73,17 @@ class CouponController extends Controller foreach ($coupons as $coupon) { $data[] = [ 'code' => $coupon, - 'type' => $coupon_type, - 'value' => $coupon_value, - 'max_uses' => $coupon_max_uses, - 'expires_at' => $coupon_datepicker, + 'type' => $request->input('type'), + 'value' => $request->input('value'), + 'max_uses' => $request->input('max_uses'), + 'expires_at' => $request->input('expires_at'), '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, - ]); + Coupon::create($request->except('_token')); } return redirect()->route('admin.coupons.index')->with('success', __("The coupon's was registered successfully.")); @@ -126,7 +108,12 @@ class CouponController extends Controller */ public function edit(Coupon $coupon) { - // + $this->checkPermission(self::WRITE_PERMISSION); + + return view('admin.coupons.edit', [ + 'coupon' => $coupon, + 'expired_at' => $coupon->expires_at ? Carbon::createFromTimestamp($coupon->expires_at) : null + ]); } /** @@ -138,7 +125,23 @@ class CouponController extends Controller */ public function update(Request $request, Coupon $coupon) { - // + $coupon_code = $request->input('code'); + $random_codes_amount = $request->input('range_codes'); + $rules = $this->requestRules($request); + + // 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()); + } + + $request->validate($rules); + $coupon->update($request->except('_token')); + + return redirect()->route('admin.coupons.index')->with('success', __('coupon has been updated!')); } /** @@ -149,11 +152,87 @@ class CouponController extends Controller */ public function destroy(Coupon $coupon) { - // + $this->checkPermission(self::WRITE_PERMISSION); + $coupon->delete(); + + return redirect()->back()->with('success', __('coupon has been removed!')); + } + + private function requestRules(Request $request) + { + $coupon_code = $request->input('code'); + $random_codes_amount = $request->input('range_codes'); + $rules = [ + "type" => "required|string|in:percentage,amount", + "max_uses" => "required|integer|digits_between:1,100", + "value" => "required|numeric|between:0,100", + "expires_at" => "nullable|date|after:" . Carbon::now()->format(Coupon::formatDate()) + ]; + + if ($coupon_code) { + $rules['code'] = "required|string|min:4"; + } elseif ($random_codes_amount) { + $rules['range_codes'] = 'required|integer|digits_between:1,100'; + } + + return $rules; } public function redeem(Request $request) { return $this->validateCoupon($request->user(), $request->input('couponCode'), $request->input('productId')); } + + public function dataTable() + { + $query = Coupon::query(); + + return datatables($query) + ->addColumn('actions', function(Coupon $coupon) { + return ' + + +
+ '; + }) + ->addColumn('status', function(Coupon $coupon) { + $color = 'success'; + $status = $coupon->getStatus(); + + if ($status != __('VALID')) { + $color = 'danger'; + } + + return ''.str_replace('_', ' ', $status).''; + }) + ->editColumn('uses', function (Coupon $coupon) { + return "{$coupon->uses} / {$coupon->max_uses}"; + }) + ->editColumn('value', function (Coupon $coupon) { + if ($coupon->type === 'percentage') { + return $coupon->value . "%"; + } + + return number_format($coupon->value, 2, '.', ''); + }) + ->editColumn('expires_at', function (Coupon $coupon) { + if (!$coupon->expires_at) { + return __('Never'); + } + + return Carbon::createFromTimestamp($coupon->expires_at); + }) + ->editColumn('created_at', function(Coupon $coupon) { + return Carbon::createFromTimeString($coupon->created_at); + }) + ->editColumn('code', function (Coupon $coupon) { + return "{$coupon->code}
";
+ })
+ ->rawColumns(['actions', 'code', 'status'])
+ ->make();
+ }
}
diff --git a/app/Listeners/CouponUsed.php b/app/Listeners/CouponUsed.php
new file mode 100644
index 00000000..75ced4eb
--- /dev/null
+++ b/app/Listeners/CouponUsed.php
@@ -0,0 +1,47 @@
+delete_coupon_on_expires = $couponSettings->delete_coupon_on_expires;
+ $this->delete_coupon_on_uses_reached = $couponSettings->delete_coupon_on_uses_reached;
+ }
+
+ /**
+ * Handle the event.
+ *
+ * @param \App\Events\CouponUsedEvent $event
+ * @return void
+ */
+ public function handle(CouponUsedEvent $event)
+ {
+ if ($this->delete_coupon_on_expires) {
+ if (!is_null($event->coupon->expired_at)) {
+ if ($event->coupon->expires_at <= Carbon::now()->timestamp) {
+ $event->coupon->delete();
+ }
+ }
+ }
+
+ if ($this->delete_coupon_on_uses_reached) {
+ if ($event->coupon->uses >= $event->coupon->max_uses) {
+ $event->coupon->delete();
+ }
+ }
+ }
+}
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index bdd71a3a..2ac9182d 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -4,6 +4,8 @@ namespace App\Providers;
use App\Events\PaymentEvent;
use App\Events\UserUpdateCreditsEvent;
+use App\Events\CouponUsedEvent;
+use App\Listeners\CouponUsed;
use App\Listeners\CreateInvoice;
use App\Listeners\UnsuspendServers;
use App\Listeners\UserPayment;
@@ -31,6 +33,9 @@ class EventServiceProvider extends ServiceProvider
CreateInvoice::class,
UserPayment::class,
],
+ CouponUsedEvent::class => [
+ CouponUsed::class
+ ],
SocialiteWasCalled::class => [
// ... other providers
'SocialiteProviders\\Discord\\DiscordExtendSocialite@handle',
diff --git a/app/Settings/CouponSettings.php b/app/Settings/CouponSettings.php
index 45f9c25b..29ec3332 100644
--- a/app/Settings/CouponSettings.php
+++ b/app/Settings/CouponSettings.php
@@ -7,6 +7,8 @@ use Spatie\LaravelSettings\Settings;
class CouponSettings extends Settings
{
public ?int $max_uses_per_user;
+ public ?bool $delete_coupon_on_expires;
+ public ?bool $delete_coupon_on_uses_reached;
public static function group(): string
{
@@ -20,7 +22,9 @@ class CouponSettings extends Settings
public static function getValidations()
{
return [
- 'max_uses_per_user' => 'required|integer'
+ 'max_uses_per_user' => 'required|integer',
+ 'delete_coupon_on_expires' => 'required|boolean',
+ 'delete_coupon_on_uses_reached' => 'required|boolean',
];
}
@@ -36,7 +40,17 @@ class CouponSettings extends Settings
'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.',
+ 'description' => 'Maximum number of uses that a user can make of the same coupon.'
+ ],
+ 'delete_coupon_on_expires' => [
+ 'label' => 'Delete Coupon On Expires',
+ 'type' => 'boolean',
+ 'description' => 'Automatically deletes the coupon if it expires.'
+ ],
+ 'delete_coupon_on_uses_reached' => [
+ 'label' => 'Delete Coupon When Max Uses Reached',
+ 'type' => 'boolean',
+ 'description' => 'Delete a coupon as soon as its maximum usage is reached.'
]
];
}
diff --git a/database/settings/2023_05_12_170041_create_coupon_settings.php b/database/settings/2023_05_12_170041_create_coupon_settings.php
index 77505cea..9ff792ce 100644
--- a/database/settings/2023_05_12_170041_create_coupon_settings.php
+++ b/database/settings/2023_05_12_170041_create_coupon_settings.php
@@ -7,10 +7,14 @@ return new class extends SettingsMigration
public function up(): void
{
$this->migrator->add('coupon.max_uses_per_user', 1);
+ $this->migrator->add('coupon.delete_coupon_on_expires', false);
+ $this->migrator->add('coupon.delete_coupon_on_uses_reached', false);
}
public function down(): void
{
$this->migrator->delete('coupon.max_uses_per_user');
+ $this->migrator->delete('coupon.delete_coupon_on_expires');
+ $this->migrator->delete('coupon.delete_coupon_on_uses_reached');
}
};
diff --git a/routes/web.php b/routes/web.php
index bdfd06f5..a6a2ab3c 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -201,6 +201,7 @@ Route::middleware(['auth', 'checkSuspended'])->group(function () {
Route::resource('partners', PartnerController::class);
//coupons
+ Route::get('coupons/datatable', [CouponController::class, 'dataTable'])->name('coupons.datatable');
Route::post('coupons/redeem', [CouponController::class, 'redeem'])->name('coupon.redeem');
Route::resource('coupons', CouponController::class);
diff --git a/themes/default/views/admin/coupons/create.blade.php b/themes/default/views/admin/coupons/create.blade.php
index 2b19d6a7..7cef744e 100644
--- a/themes/default/views/admin/coupons/create.blade.php
+++ b/themes/default/views/admin/coupons/create.blade.php
@@ -84,7 +84,7 @@
@enderror
{{__('Partner discount')}} | -{{__('Registered user discount')}} | -{{__('Referral system commission')}} | -{{__('Created')}} | +{{__('Status')}} | +{{__('Code')}} | +{{__('Value')}} | +{{__('Used / Max Uses')}} | +{{__('Expires')}} | +{{__('Created At')}} | {{__('Actions')}} |
---|