Pārlūkot izejas kodu

Added Voucher system

AVMG20 4 gadi atpakaļ
vecāks
revīzija
55b2223435

+ 1 - 1
app/Http/Controllers/Admin/UserController.php

@@ -78,7 +78,7 @@ class UserController extends Controller
             "name" => "required|string|min:4|max:30",
             "pterodactyl_id" => "required|numeric|unique:users,pterodactyl_id,{$user->id}",
             "email" => "required|string|email",
-            "credits" => "required|numeric|min:0|max:999999",
+            "credits" => "required|numeric|min:0|max:99999999",
             "server_limit" => "required|numeric|min:0|max:1000000",
             "role" => Rule::in(['admin', 'mod', 'client', 'member']),
         ]);

+ 54 - 9
app/Http/Controllers/Admin/VoucherController.php

@@ -7,9 +7,13 @@ use App\Models\Voucher;
 use Illuminate\Contracts\Foundation\Application;
 use Illuminate\Contracts\View\Factory;
 use Illuminate\Contracts\View\View;
+use Illuminate\Http\JsonResponse;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
 use Illuminate\Http\Response;
+use Illuminate\Support\Facades\Validator;
+use Illuminate\Validation\ValidationData;
+use Illuminate\Validation\ValidationException;
 
 class VoucherController extends Controller
 {
@@ -42,11 +46,11 @@ class VoucherController extends Controller
     public function store(Request $request)
     {
         $request->validate([
-            'memo' => 'sometimes|string|max:191',
-            'code' => 'required|string|alpha_dash|max:36',
-            'uses' => 'required|numeric|max:2147483647',
-            'credits' => 'required|numeric|between:0,99999999',
-            'expires_at' => 'required|date|after:today',
+            'memo'       => 'sometimes|string|max:191',
+            'code'       => 'required|string|alpha_dash|max:36',
+            'uses'       => 'required|numeric|max:2147483647',
+            'credits'    => 'required|numeric|between:0,99999999',
+            'expires_at' => 'nullable|date|after:1 hour',
         ]);
 
         Voucher::create($request->except('_token'));
@@ -100,9 +104,49 @@ class VoucherController extends Controller
         return redirect()->back()->with('success', 'voucher has been removed!');
     }
 
+    /**
+     * @param Request $request
+     * @return JsonResponse
+     * @throws ValidationException
+     */
+    public function redeem(Request $request)
+    {
+        #general validations
+        $request->validate([
+            'code' => 'required|exists:vouchers,code'
+        ]);
+
+        #get voucher by code
+        $voucher = Voucher::where('code' , '=' , $request->input('code'))->firstOrFail();
+
+        #extra validations
+        if ($voucher->getStatus() == 'USES_LIMIT_REACHED') throw ValidationException::withMessages([
+            'code' => 'This voucher has reached the maximum amount of uses'
+        ]);
+
+        if ($voucher->getStatus() == 'EXPIRED') throw ValidationException::withMessages([
+            'code' => 'This voucher has expired'
+        ]);
+
+        if (!$request->user()->vouchers()->where('id' , '=' , $voucher->id)->get()->isEmpty()) throw ValidationException::withMessages([
+            'code' => 'You already redeemed this voucher code'
+        ]);
+
+        if ($request->user()->credits + $voucher->credits >= 99999999) throw ValidationException::withMessages([
+            'code' => "You can't redeem a voucher with this many credits"
+        ]);
+
+        #redeem voucher
+        $voucher->redeem($request->user());
+
+        return response()->json([
+            'success' => "{$voucher->credits} credits have been added to your balance!"
+        ]);
+    }
+
     public function dataTable()
     {
-        $query = Voucher::with(['users']);
+        $query = Voucher::query();
 
         return datatables($query)
             ->addColumn('actions', function (Voucher $voucher) {
@@ -120,7 +164,7 @@ class VoucherController extends Controller
             ->addColumn('status', function (Voucher $voucher) {
                 $color = 'success';
                 if ($voucher->getStatus() != 'VALID') $color = 'danger';
-                return '<span class="badge badge-'.$color.'">'. $voucher->getStatus() .'</span>';
+                return '<span class="badge badge-' . $color . '">' . $voucher->getStatus() . '</span>';
             })
             ->editColumn('uses', function (Voucher $voucher) {
                 $userCount = $voucher->users()->count();
@@ -130,12 +174,13 @@ class VoucherController extends Controller
                 return number_format($voucher->credits, 2, '.', '');
             })
             ->editColumn('expires_at', function (Voucher $voucher) {
+                if (!$voucher->expires_at) return "";
                 return $voucher->expires_at ? $voucher->expires_at->diffForHumans() : '';
             })
-            ->editColumn('code' , function (Voucher $voucher) {
+            ->editColumn('code', function (Voucher $voucher) {
                 return "<code>{$voucher->code}</code>";
             })
-            ->rawColumns(['actions' , 'code' , 'status'])
+            ->rawColumns(['actions', 'code', 'status'])
             ->make();
     }
 

+ 47 - 0
app/Models/User.php

@@ -7,17 +7,30 @@ use App\Notifications\Auth\QueuedVerifyEmail;
 use App\Notifications\WelcomeMessage;
 use Illuminate\Contracts\Auth\MustVerifyEmail;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Relations\BelongsToMany;
+use Illuminate\Database\Eloquent\Relations\HasMany;
+use Illuminate\Database\Eloquent\Relations\HasOne;
 use Illuminate\Foundation\Auth\User as Authenticatable;
 use Illuminate\Notifications\Notifiable;
 use Spatie\Activitylog\Traits\CausesActivity;
 use Spatie\Activitylog\Traits\LogsActivity;
 
+/**
+ * Class User
+ * @package App\Models
+ */
 class User extends Authenticatable implements MustVerifyEmail
 {
     use HasFactory, Notifiable, LogsActivity, CausesActivity;
 
+    /**
+     * @var string[]
+     */
     protected static $logAttributes = ['name', 'email'];
 
+    /**
+     * @var string[]
+     */
     protected static $ignoreChangedAttributes = [
         'remember_token',
         'credits',
@@ -68,6 +81,9 @@ class User extends Authenticatable implements MustVerifyEmail
         'last_seen' => 'datetime',
     ];
 
+    /**
+     *
+     */
     public static function boot()
     {
         parent::boot();
@@ -93,20 +109,32 @@ class User extends Authenticatable implements MustVerifyEmail
         });
     }
 
+    /**
+     *
+     */
     public function sendEmailVerificationNotification()
     {
         $this->notify(new QueuedVerifyEmail);
     }
 
+    /**
+     * @return string
+     */
     public function credits()
     {
         return number_format($this->credits, 2, '.', '');
     }
 
+    /**
+     * @return string
+     */
     public function getAvatar(){
         return "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email)));
     }
 
+    /**
+     * @return string
+     */
     public function creditUsage()
     {
         $usage = 0;
@@ -118,6 +146,9 @@ class User extends Authenticatable implements MustVerifyEmail
         return number_format($usage, 2, '.', '');
     }
 
+    /**
+     * @return array|string|string[]
+     */
     public function getVerifiedStatus(){
         $status = '';
         if ($this->hasVerifiedEmail()) $status .= 'email ';
@@ -126,15 +157,31 @@ class User extends Authenticatable implements MustVerifyEmail
         return $status;
     }
 
+    /**
+     * @return BelongsToMany
+     */
+    public function vouchers(){
+        return $this->belongsToMany(Voucher::class);
+    }
+
+    /**
+     * @return HasOne
+     */
     public function discordUser(){
         return $this->hasOne(DiscordUser::class);
     }
 
+    /**
+     * @return HasMany
+     */
     public function servers()
     {
         return $this->hasMany(Server::class);
     }
 
+    /**
+     * @return HasMany
+     */
     public function payments()
     {
         return $this->hasMany(Payment::class);

+ 23 - 2
app/Models/Voucher.php

@@ -41,14 +41,35 @@ class Voucher extends Model
         });
     }
 
+    /**
+     * @return string
+     */
     public function getStatus(){
         if ($this->users()->count() >= $this->uses) return 'USES_LIMIT_REACHED';
-        if ($this->expires_at->isPast()) return 'EXPIRED';
+        if (!is_null($this->expires_at)){
+            if ($this->expires_at->isPast()) return 'EXPIRED';
+        }
+
         return 'VALID';
     }
 
     /**
-     * @return BelongsToMany
+     * @param User $user
+     * @return float
+     */
+    public function redeem(User $user){
+        try {
+            $user->increment('credits' , $this->credits);
+            $this->users()->attach($user);
+        }catch (\Exception $exception) {
+            throw $exception;
+        }
+
+        return $this->credits;
+    }
+
+    /**
+     * @return \Illuminate\Database\Eloquent\Relations\HasMany
      */
     public function users()
     {

+ 32 - 0
database/migrations/2021_07_10_062140_update_credits_to_users_table.php

@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class UpdateCreditsToUsersTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->unsignedFloat('credits', 10)->change();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->unsignedFloat('credits')->change();
+        });
+    }
+}

+ 1 - 1
resources/views/admin/users/edit.blade.php

@@ -72,7 +72,7 @@
                                 <div class="form-group">
                                     <label for="credits">Credits</label>
                                     <input value="{{$user->credits}}" id="credits" name="credits" step="any" min="0"
-                                           max="999999"
+                                           max="99999999"
                                            type="number" class="form-control @error('credits') is-invalid @enderror"
                                            required="required">
                                     @error('credits')

+ 3 - 3
resources/views/admin/vouchers/create.blade.php

@@ -38,7 +38,7 @@
                                 @csrf
 
                                 <div class="form-group">
-                                    <label for="memo">Memo</label>
+                                    <label for="memo">Memo <i data-toggle="popover" data-trigger="hover" data-content="Only admins can see this" class="fas fa-info-circle"></i></label>
                                     <input value="{{old('memo')}}" placeholder="Summer break voucher" id="memo"
                                            name="memo" type="text"
                                            class="form-control @error('memo') is-invalid @enderror">
@@ -53,7 +53,7 @@
                                     <label for="credits">Credits *</label>
                                     <input value="{{old('credits')}}" placeholder="500" id="credits"
                                            name="credits" type="number" step="any" min="0"
-                                           max="999999"
+                                           max="99999999"
                                            class="form-control @error('credits') is-invalid @enderror">
                                     @error('credits')
                                     <div class="text-danger">
@@ -84,7 +84,7 @@
                                 </div>
 
                                 <div class="form-group">
-                                    <label for="uses">Uses *</label>
+                                    <label for="uses">Uses * <i data-toggle="popover" data-trigger="hover" data-content="A voucher can only be used one time per user. Uses specifies the number of different users that can use this voucher." class="fas fa-info-circle"></i></label>
                                     <div class="input-group">
                                         <input value="{{old('uses') ?? 1}}" id="uses" min="1" max="2147483647"
                                                name="uses" type="number"

+ 60 - 6
resources/views/models/redeem_voucher_modal.blade.php

@@ -16,17 +16,19 @@
 
             <!-- Modal body -->
             <div class="modal-body">
-                <form>
+                <form id="redeemVoucherForm" onsubmit="return false" method="post" action="{{route('voucher.redeem')}}">
                     <div class="form-group">
-                        <label for="code">Code</label>
+                        <label for="redeemVoucherCode">Code</label>
                         <div class="input-group">
                             <div class="input-group-prepend">
                                 <div class="input-group-text">
                                     <i class="fas fa-money-check-alt"></i>
                                 </div>
                             </div>
-                            <input id="code" name="code" placeholder="SUMMER" type="text" class="form-control">
+                            <input id="redeemVoucherCode" name="code" placeholder="SUMMER" type="text" class="form-control">
                         </div>
+                        <span id="redeemVoucherCodeError" class="text-danger"></span>
+                        <span id="redeemVoucherCodeSuccess" class="text-success"></span>
                     </div>
                 </form>
             </div>
@@ -34,7 +36,7 @@
             <!-- Modal footer -->
             <div class="modal-footer">
                 <button type="button" class="btn btn-danger" data-dismiss="modal">Close</button>
-                <button name="submit" type="button" class="btn btn-primary">Redeem</button>
+                <button name="submit" id="redeemVoucherSubmit"  onclick="redeemVoucherCode()" type="button" class="btn btn-primary">Redeem</button>
             </div>
 
         </div>
@@ -43,9 +45,61 @@
 
 
 <script>
-    function validateCode(){
+    function redeemVoucherCode(){
+        let form = document.getElementById('redeemVoucherForm')
+        let button = document.getElementById('redeemVoucherSubmit')
+        let input = document.getElementById('redeemVoucherCode')
+
+        console.log(form.method , form.action)
+        button.disabled = true
+
         $.ajax({
-            
+            method : form.method,
+            url : form.action,
+            dataType: 'json',
+            data: {
+                code : input.value
+            },
+            success : function (response) {
+                resetForm()
+                redeemVoucherSetSuccess(response)
+            },
+            error : function (jqXHR, textStatus, errorThrown) {
+                resetForm()
+                redeemVoucherSetError(jqXHR.responseJSON)
+                console.error(jqXHR.responseJSON)
+            },
+
         })
     }
+
+    function resetForm(){
+        let button = document.getElementById('redeemVoucherSubmit')
+        let input = document.getElementById('redeemVoucherCode')
+        let successLabel = document.getElementById('redeemVoucherCodeSuccess')
+        let errorLabel = document.getElementById('redeemVoucherCodeError')
+
+        input.classList.remove('is-invalid')
+        input.classList.remove('is-valid')
+        successLabel.innerHTML = ''
+        errorLabel.innerHTML = ''
+        button.disabled = false
+    }
+
+    function redeemVoucherSetError(error){
+        let input = document.getElementById('redeemVoucherCode')
+        let errorLabel = document.getElementById('redeemVoucherCodeError')
+
+        input.classList.add("is-invalid")
+        errorLabel.innerHTML = error.errors.code[0]
+    }
+
+    function redeemVoucherSetSuccess(response){
+        let input = document.getElementById('redeemVoucherCode')
+        let successLabel = document.getElementById('redeemVoucherCodeSuccess')
+
+        successLabel.innerHTML = response.success
+        input.classList.remove('is-invalid')
+        input.classList.add('is-valid')
+    }
 </script>

+ 8 - 0
routes/web.php

@@ -66,6 +66,14 @@ Route::middleware('auth')->group(function () {
     Route::get('/auth/redirect', [SocialiteController::class, 'redirect'])->name('auth.redirect');
     Route::get('/auth/callback', [SocialiteController::class, 'callback'])->name('auth.callback');
 
+    #voucher redeem
+    Route::post('/voucher/redeem' , [VoucherController::class , 'redeem'])->name('voucher.redeem');
+
+    Route::get('/test' , function (Request $request) {
+        $voucher = \App\Models\Voucher::first();
+        dd($request->user()->vouchers()->where('id' , '=' , $voucher->id)->get()->isEmpty());
+    });
+
     #admin
     Route::prefix('admin')->name('admin.')->middleware('admin')->group(function () {