Added Voucher system
This commit is contained in:
parent
a7f8d71f92
commit
55b2223435
|
@ -78,7 +78,7 @@ class UserController extends Controller
|
||||||
"name" => "required|string|min:4|max:30",
|
"name" => "required|string|min:4|max:30",
|
||||||
"pterodactyl_id" => "required|numeric|unique:users,pterodactyl_id,{$user->id}",
|
"pterodactyl_id" => "required|numeric|unique:users,pterodactyl_id,{$user->id}",
|
||||||
"email" => "required|string|email",
|
"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",
|
"server_limit" => "required|numeric|min:0|max:1000000",
|
||||||
"role" => Rule::in(['admin', 'mod', 'client', 'member']),
|
"role" => Rule::in(['admin', 'mod', 'client', 'member']),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -7,9 +7,13 @@ use App\Models\Voucher;
|
||||||
use Illuminate\Contracts\Foundation\Application;
|
use Illuminate\Contracts\Foundation\Application;
|
||||||
use Illuminate\Contracts\View\Factory;
|
use Illuminate\Contracts\View\Factory;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
use Illuminate\Validation\ValidationData;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
class VoucherController extends Controller
|
class VoucherController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -46,7 +50,7 @@ class VoucherController extends Controller
|
||||||
'code' => 'required|string|alpha_dash|max:36',
|
'code' => 'required|string|alpha_dash|max:36',
|
||||||
'uses' => 'required|numeric|max:2147483647',
|
'uses' => 'required|numeric|max:2147483647',
|
||||||
'credits' => 'required|numeric|between:0,99999999',
|
'credits' => 'required|numeric|between:0,99999999',
|
||||||
'expires_at' => 'required|date|after:today',
|
'expires_at' => 'nullable|date|after:1 hour',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Voucher::create($request->except('_token'));
|
Voucher::create($request->except('_token'));
|
||||||
|
@ -100,9 +104,49 @@ class VoucherController extends Controller
|
||||||
return redirect()->back()->with('success', 'voucher has been removed!');
|
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()
|
public function dataTable()
|
||||||
{
|
{
|
||||||
$query = Voucher::with(['users']);
|
$query = Voucher::query();
|
||||||
|
|
||||||
return datatables($query)
|
return datatables($query)
|
||||||
->addColumn('actions', function (Voucher $voucher) {
|
->addColumn('actions', function (Voucher $voucher) {
|
||||||
|
@ -130,6 +174,7 @@ class VoucherController extends Controller
|
||||||
return number_format($voucher->credits, 2, '.', '');
|
return number_format($voucher->credits, 2, '.', '');
|
||||||
})
|
})
|
||||||
->editColumn('expires_at', function (Voucher $voucher) {
|
->editColumn('expires_at', function (Voucher $voucher) {
|
||||||
|
if (!$voucher->expires_at) return "";
|
||||||
return $voucher->expires_at ? $voucher->expires_at->diffForHumans() : '';
|
return $voucher->expires_at ? $voucher->expires_at->diffForHumans() : '';
|
||||||
})
|
})
|
||||||
->editColumn('code', function (Voucher $voucher) {
|
->editColumn('code', function (Voucher $voucher) {
|
||||||
|
|
|
@ -7,17 +7,30 @@ use App\Notifications\Auth\QueuedVerifyEmail;
|
||||||
use App\Notifications\WelcomeMessage;
|
use App\Notifications\WelcomeMessage;
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
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\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Spatie\Activitylog\Traits\CausesActivity;
|
use Spatie\Activitylog\Traits\CausesActivity;
|
||||||
use Spatie\Activitylog\Traits\LogsActivity;
|
use Spatie\Activitylog\Traits\LogsActivity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class User
|
||||||
|
* @package App\Models
|
||||||
|
*/
|
||||||
class User extends Authenticatable implements MustVerifyEmail
|
class User extends Authenticatable implements MustVerifyEmail
|
||||||
{
|
{
|
||||||
use HasFactory, Notifiable, LogsActivity, CausesActivity;
|
use HasFactory, Notifiable, LogsActivity, CausesActivity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
protected static $logAttributes = ['name', 'email'];
|
protected static $logAttributes = ['name', 'email'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
protected static $ignoreChangedAttributes = [
|
protected static $ignoreChangedAttributes = [
|
||||||
'remember_token',
|
'remember_token',
|
||||||
'credits',
|
'credits',
|
||||||
|
@ -68,6 +81,9 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||||
'last_seen' => 'datetime',
|
'last_seen' => 'datetime',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
public static function boot()
|
public static function boot()
|
||||||
{
|
{
|
||||||
parent::boot();
|
parent::boot();
|
||||||
|
@ -93,20 +109,32 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
public function sendEmailVerificationNotification()
|
public function sendEmailVerificationNotification()
|
||||||
{
|
{
|
||||||
$this->notify(new QueuedVerifyEmail);
|
$this->notify(new QueuedVerifyEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function credits()
|
public function credits()
|
||||||
{
|
{
|
||||||
return number_format($this->credits, 2, '.', '');
|
return number_format($this->credits, 2, '.', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getAvatar(){
|
public function getAvatar(){
|
||||||
return "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email)));
|
return "https://www.gravatar.com/avatar/" . md5(strtolower(trim($this->email)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function creditUsage()
|
public function creditUsage()
|
||||||
{
|
{
|
||||||
$usage = 0;
|
$usage = 0;
|
||||||
|
@ -118,6 +146,9 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||||
return number_format($usage, 2, '.', '');
|
return number_format($usage, 2, '.', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|string|string[]
|
||||||
|
*/
|
||||||
public function getVerifiedStatus(){
|
public function getVerifiedStatus(){
|
||||||
$status = '';
|
$status = '';
|
||||||
if ($this->hasVerifiedEmail()) $status .= 'email ';
|
if ($this->hasVerifiedEmail()) $status .= 'email ';
|
||||||
|
@ -126,15 +157,31 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||||
return $status;
|
return $status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BelongsToMany
|
||||||
|
*/
|
||||||
|
public function vouchers(){
|
||||||
|
return $this->belongsToMany(Voucher::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasOne
|
||||||
|
*/
|
||||||
public function discordUser(){
|
public function discordUser(){
|
||||||
return $this->hasOne(DiscordUser::class);
|
return $this->hasOne(DiscordUser::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasMany
|
||||||
|
*/
|
||||||
public function servers()
|
public function servers()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Server::class);
|
return $this->hasMany(Server::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasMany
|
||||||
|
*/
|
||||||
public function payments()
|
public function payments()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Payment::class);
|
return $this->hasMany(Payment::class);
|
||||||
|
|
|
@ -41,14 +41,35 @@ class Voucher extends Model
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
public function getStatus(){
|
public function getStatus(){
|
||||||
if ($this->users()->count() >= $this->uses) return 'USES_LIMIT_REACHED';
|
if ($this->users()->count() >= $this->uses) return 'USES_LIMIT_REACHED';
|
||||||
|
if (!is_null($this->expires_at)){
|
||||||
if ($this->expires_at->isPast()) return 'EXPIRED';
|
if ($this->expires_at->isPast()) return 'EXPIRED';
|
||||||
|
}
|
||||||
|
|
||||||
return 'VALID';
|
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()
|
public function users()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -72,7 +72,7 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="credits">Credits</label>
|
<label for="credits">Credits</label>
|
||||||
<input value="{{$user->credits}}" id="credits" name="credits" step="any" min="0"
|
<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"
|
type="number" class="form-control @error('credits') is-invalid @enderror"
|
||||||
required="required">
|
required="required">
|
||||||
@error('credits')
|
@error('credits')
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<div class="form-group">
|
<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"
|
<input value="{{old('memo')}}" placeholder="Summer break voucher" id="memo"
|
||||||
name="memo" type="text"
|
name="memo" type="text"
|
||||||
class="form-control @error('memo') is-invalid @enderror">
|
class="form-control @error('memo') is-invalid @enderror">
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
<label for="credits">Credits *</label>
|
<label for="credits">Credits *</label>
|
||||||
<input value="{{old('credits')}}" placeholder="500" id="credits"
|
<input value="{{old('credits')}}" placeholder="500" id="credits"
|
||||||
name="credits" type="number" step="any" min="0"
|
name="credits" type="number" step="any" min="0"
|
||||||
max="999999"
|
max="99999999"
|
||||||
class="form-control @error('credits') is-invalid @enderror">
|
class="form-control @error('credits') is-invalid @enderror">
|
||||||
@error('credits')
|
@error('credits')
|
||||||
<div class="text-danger">
|
<div class="text-danger">
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<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">
|
<div class="input-group">
|
||||||
<input value="{{old('uses') ?? 1}}" id="uses" min="1" max="2147483647"
|
<input value="{{old('uses') ?? 1}}" id="uses" min="1" max="2147483647"
|
||||||
name="uses" type="number"
|
name="uses" type="number"
|
||||||
|
|
|
@ -16,17 +16,19 @@
|
||||||
|
|
||||||
<!-- Modal body -->
|
<!-- Modal body -->
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form>
|
<form id="redeemVoucherForm" onsubmit="return false" method="post" action="{{route('voucher.redeem')}}">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="code">Code</label>
|
<label for="redeemVoucherCode">Code</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<div class="input-group-text">
|
<div class="input-group-text">
|
||||||
<i class="fas fa-money-check-alt"></i>
|
<i class="fas fa-money-check-alt"></i>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
<span id="redeemVoucherCodeError" class="text-danger"></span>
|
||||||
|
<span id="redeemVoucherCodeSuccess" class="text-success"></span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,7 +36,7 @@
|
||||||
<!-- Modal footer -->
|
<!-- Modal footer -->
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-danger" data-dismiss="modal">Close</button>
|
<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>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,9 +45,61 @@
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<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({
|
$.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>
|
</script>
|
||||||
|
|
|
@ -66,6 +66,14 @@ Route::middleware('auth')->group(function () {
|
||||||
Route::get('/auth/redirect', [SocialiteController::class, 'redirect'])->name('auth.redirect');
|
Route::get('/auth/redirect', [SocialiteController::class, 'redirect'])->name('auth.redirect');
|
||||||
Route::get('/auth/callback', [SocialiteController::class, 'callback'])->name('auth.callback');
|
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
|
#admin
|
||||||
Route::prefix('admin')->name('admin.')->middleware('admin')->group(function () {
|
Route::prefix('admin')->name('admin.')->middleware('admin')->group(function () {
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue