Add all files
This commit is contained in:
25
app/Models/ApiKey.php
Normal file
25
app/Models/ApiKey.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ApiKey extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'permissions',
|
||||
'token',
|
||||
'user_id',
|
||||
'type',
|
||||
'ip_addresses',
|
||||
'last_used_at',
|
||||
'enabled',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'permissions' => 'array',
|
||||
'ip_addresses' => 'array',
|
||||
'last_used_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
42
app/Models/AuditLog.php
Normal file
42
app/Models/AuditLog.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AuditLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'action',
|
||||
'description',
|
||||
'model_type',
|
||||
'model_id',
|
||||
'user_id',
|
||||
'changes',
|
||||
'ip_address',
|
||||
'user_agent',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'changes' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the model that was changed.
|
||||
*/
|
||||
public function model()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that performed the action.
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
42
app/Models/Category.php
Normal file
42
app/Models/Category.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Observers\CategoryObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
#[ObservedBy(CategoryObserver::class)]
|
||||
class Category extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* Get the products of the category.
|
||||
*/
|
||||
public function products(): HasMany
|
||||
{
|
||||
return $this->hasMany(Product::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent category of the category.
|
||||
*/
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class, 'parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the children categories of the category.
|
||||
*/
|
||||
public function children(): HasMany
|
||||
{
|
||||
return $this->hasMany(Category::class, 'parent_id');
|
||||
}
|
||||
}
|
||||
56
app/Models/ConfigOption.php
Normal file
56
app/Models/ConfigOption.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\HasPlans;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ConfigOption extends Model
|
||||
{
|
||||
use HasFactory, HasPlans;
|
||||
|
||||
protected $dontShowUnavailablePrice = true;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'env_variable',
|
||||
'type',
|
||||
'sort',
|
||||
'hidden',
|
||||
'parent_id',
|
||||
'upgradable',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the parent option.
|
||||
*/
|
||||
public function parent()
|
||||
{
|
||||
return $this->belongsTo(ConfigOption::class, 'parent_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options that belong to the parent. (children or options)
|
||||
*/
|
||||
public function children()
|
||||
{
|
||||
return $this->hasMany(ConfigOption::class, 'parent_id')->orderBy('sort');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the products that belong to the option.
|
||||
*/
|
||||
public function products()
|
||||
{
|
||||
return $this->belongsToMany(Product::class, 'config_option_products');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the service configs that belong to the option.
|
||||
*/
|
||||
public function serviceConfigs()
|
||||
{
|
||||
return $this->hasMany(ServiceConfig::class, 'config_option_id');
|
||||
}
|
||||
}
|
||||
34
app/Models/ConfigOptionProduct.php
Normal file
34
app/Models/ConfigOptionProduct.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ConfigOptionProduct extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'config_option_id',
|
||||
'product_id',
|
||||
];
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* Get the option of the product.
|
||||
*/
|
||||
public function configOption()
|
||||
{
|
||||
return $this->belongsTo(ConfigOption::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product of the option.
|
||||
*/
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
59
app/Models/Coupon.php
Normal file
59
app/Models/Coupon.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Coupon extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'type',
|
||||
'time',
|
||||
'code',
|
||||
'value',
|
||||
'max_uses',
|
||||
'max_uses_per_user',
|
||||
'starts_at',
|
||||
'expires_at',
|
||||
'recurring',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'starts_at' => 'datetime',
|
||||
'expires_at' => 'datetime',
|
||||
'max_uses' => 'integer',
|
||||
'max_uses_per_user' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the products that belong to the option.
|
||||
*/
|
||||
public function products()
|
||||
{
|
||||
return $this->belongsToMany(Product::class, 'coupon_products');
|
||||
}
|
||||
|
||||
public function services()
|
||||
{
|
||||
return $this->hasMany(Service::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user has exceeded the maximum allowed uses of this coupon
|
||||
*
|
||||
* @param int $userId
|
||||
*/
|
||||
public function hasExceededMaxUsesPerUser($userId): bool
|
||||
{
|
||||
if (empty($this->max_uses_per_user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->services()
|
||||
->where('user_id', $userId)
|
||||
->count() >= $this->max_uses_per_user;
|
||||
}
|
||||
}
|
||||
26
app/Models/CouponProduct.php
Normal file
26
app/Models/CouponProduct.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CouponProduct extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'coupon_id',
|
||||
'product_id',
|
||||
];
|
||||
|
||||
public function coupon()
|
||||
{
|
||||
return $this->belongsTo(Coupon::class);
|
||||
}
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
}
|
||||
32
app/Models/Credit.php
Normal file
32
app/Models/Credit.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Classes\Price;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Credit extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'currency_code',
|
||||
'amount',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class, 'currency_code', 'code');
|
||||
}
|
||||
|
||||
public function formattedAmount(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => new Price(['price' => $this->amount, 'currency' => $this->currency])
|
||||
);
|
||||
}
|
||||
}
|
||||
26
app/Models/Currency.php
Normal file
26
app/Models/Currency.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Currency extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
protected $keyType = 'string';
|
||||
|
||||
protected $primaryKey = 'code';
|
||||
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'prefix',
|
||||
'suffix',
|
||||
'format',
|
||||
];
|
||||
}
|
||||
19
app/Models/CustomProperty.php
Normal file
19
app/Models/CustomProperty.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CustomProperty extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
public $guarded = [];
|
||||
|
||||
public $casts = [
|
||||
'allowed_values' => 'array',
|
||||
];
|
||||
}
|
||||
17
app/Models/DebugLog.php
Normal file
17
app/Models/DebugLog.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DebugLog extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'context',
|
||||
'type',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'context' => 'json',
|
||||
];
|
||||
}
|
||||
22
app/Models/EmailLog.php
Normal file
22
app/Models/EmailLog.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EmailLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'subject',
|
||||
'body',
|
||||
'to',
|
||||
'sent_at',
|
||||
'status',
|
||||
'error',
|
||||
'job_uuid',
|
||||
];
|
||||
}
|
||||
27
app/Models/EmailTemplate.php
Normal file
27
app/Models/EmailTemplate.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class EmailTemplate extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'key',
|
||||
'name',
|
||||
'subject',
|
||||
'enabled',
|
||||
'body',
|
||||
'cc',
|
||||
'bcc',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'enabled' => 'boolean',
|
||||
'cc' => 'array',
|
||||
'bcc' => 'array',
|
||||
];
|
||||
}
|
||||
44
app/Models/Extension.php
Normal file
44
app/Models/Extension.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Extension extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'enabled',
|
||||
// Name of extension class (e.g. 'Stripe' or 'Paypal')
|
||||
'extension',
|
||||
'type',
|
||||
];
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* Get the extension's settings.
|
||||
*/
|
||||
public function settings()
|
||||
{
|
||||
return $this->morphMany(Setting::class, 'settingable');
|
||||
}
|
||||
|
||||
public function path(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => ucfirst($this->type) . 's/' . ucfirst($this->extension)
|
||||
);
|
||||
}
|
||||
|
||||
public function namespace(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => 'Paymenter\\Extensions\\' . ucfirst($this->type) . 's\\' . ucfirst($this->extension)
|
||||
);
|
||||
}
|
||||
}
|
||||
36
app/Models/FailedJob.php
Normal file
36
app/Models/FailedJob.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
class FailedJob extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'connection',
|
||||
'queue',
|
||||
'payload',
|
||||
'exception',
|
||||
'failed_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'failed_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function retry()
|
||||
{
|
||||
try {
|
||||
Artisan::call('queue:retry', ['id' => $this->uuid]);
|
||||
} catch (\Exception $e) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
// $this->delete();
|
||||
}
|
||||
}
|
||||
13
app/Models/Gateway.php
Normal file
13
app/Models/Gateway.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class Gateway extends Extension
|
||||
{
|
||||
protected $table = 'extensions';
|
||||
|
||||
public function newQuery($excludeDeleted = true)
|
||||
{
|
||||
return parent::newQuery($excludeDeleted)->where('type', 'gateway');
|
||||
}
|
||||
}
|
||||
102
app/Models/Invoice.php
Normal file
102
app/Models/Invoice.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Classes\PDF;
|
||||
use App\Classes\Price;
|
||||
use App\Observers\InvoiceObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[ObservedBy([InvoiceObserver::class])]
|
||||
class Invoice extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const STATUS_PENDING = 'pending';
|
||||
|
||||
public const STATUS_PAID = 'paid';
|
||||
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
|
||||
protected $fillable = ['number', 'user_id', 'currency_code', 'due_at', 'status'];
|
||||
|
||||
protected $casts = [
|
||||
'due_at' => 'date',
|
||||
];
|
||||
|
||||
public bool $send_create_email = true;
|
||||
|
||||
/**
|
||||
* Total of the invoice.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function total(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->items->sum(fn ($item) => $item->price * $item->quantity)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Total of the invoice.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formattedTotal(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => new Price(['price' => $this->total, 'currency' => $this->currency])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted remaining amount of the invoice.
|
||||
*/
|
||||
public function formattedRemaining(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => new Price(['price' => $this->remaining, 'currency' => $this->currency])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remaining amount of the invoice.
|
||||
*/
|
||||
public function remaining(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->total - $this->transactions->sum('amount')
|
||||
);
|
||||
}
|
||||
|
||||
public function currency()
|
||||
{
|
||||
return $this->belongsTo(Currency::class, 'currency_code');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(InvoiceItem::class);
|
||||
}
|
||||
|
||||
public function transactions()
|
||||
{
|
||||
return $this->hasMany(InvoiceTransaction::class);
|
||||
}
|
||||
|
||||
public function pdf(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => PDF::generateInvoice($this)
|
||||
);
|
||||
}
|
||||
}
|
||||
61
app/Models/InvoiceItem.php
Normal file
61
app/Models/InvoiceItem.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Classes\Price;
|
||||
use App\Observers\InvoiceItemObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[ObservedBy([InvoiceItemObserver::class])]
|
||||
class InvoiceItem extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'invoice_id',
|
||||
'service_id',
|
||||
'quantity',
|
||||
'price',
|
||||
'description',
|
||||
'gateway_id',
|
||||
'reference_id',
|
||||
'reference_type',
|
||||
];
|
||||
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo(Invoice::class);
|
||||
}
|
||||
|
||||
public function reference()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function gateway()
|
||||
{
|
||||
return $this->belongsTo(Gateway::class);
|
||||
}
|
||||
|
||||
public function total()
|
||||
{
|
||||
return $this->price * $this->quantity;
|
||||
}
|
||||
|
||||
public function formattedTotal(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => new Price(['price' => $this->total(), 'currency' => $this->invoice->currency])
|
||||
);
|
||||
}
|
||||
|
||||
public function formattedPrice(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => new Price(['price' => $this->price, 'currency' => $this->invoice->currency])
|
||||
);
|
||||
}
|
||||
}
|
||||
54
app/Models/InvoiceTransaction.php
Normal file
54
app/Models/InvoiceTransaction.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Classes\Price;
|
||||
use App\Observers\InvoiceTransactionObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[ObservedBy([InvoiceTransactionObserver::class])]
|
||||
class InvoiceTransaction extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'invoice_id',
|
||||
'gateway_id',
|
||||
'amount',
|
||||
'fee',
|
||||
'transaction_id',
|
||||
];
|
||||
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo(Invoice::class);
|
||||
}
|
||||
|
||||
public function gateway()
|
||||
{
|
||||
return $this->belongsTo(Gateway::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted remaining amount of the invoice.
|
||||
*/
|
||||
public function formattedFee(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => new Price(['price' => $this->fee, 'currency' => $this->invoice->currency])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatted remaining amount of the invoice.
|
||||
*/
|
||||
public function formattedAmount(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => new Price(['price' => $this->amount, 'currency' => $this->invoice->currency])
|
||||
);
|
||||
}
|
||||
}
|
||||
7
app/Models/OauthClient.php
Normal file
7
app/Models/OauthClient.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Laravel\Passport\Client as PassportClient;
|
||||
|
||||
class OauthClient extends PassportClient {}
|
||||
75
app/Models/Order.php
Normal file
75
app/Models/Order.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Classes\Price;
|
||||
use App\Observers\OrderObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[ObservedBy([OrderObserver::class])]
|
||||
class Order extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['user_id', 'currency_code'];
|
||||
|
||||
public bool $send_create_email = true;
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function services()
|
||||
{
|
||||
return $this->hasMany(Service::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currency corresponding to the service.
|
||||
*/
|
||||
public function currency()
|
||||
{
|
||||
return $this->hasOne(Currency::class, 'code', 'currency_code');
|
||||
}
|
||||
|
||||
/**
|
||||
* Total of the order.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function total(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->services->sum(fn ($service) => $service->price * $service->quantity)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Total of the order.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formattedTotal(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => new Price(['price' => $this->total, 'currency' => $this->currency])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all invoices for the order.
|
||||
*/
|
||||
public function invoices(): Attribute
|
||||
{
|
||||
// Each service has invoices (it is a hasManyThrough relationship order -> service -> invoiceItem -> invoice)
|
||||
$invoicesId = $this->services->map(fn ($service) => $service->invoiceItems->map(fn ($invoiceItem) => $invoiceItem->invoice_id))->flatten();
|
||||
|
||||
return new Attribute(
|
||||
get: fn () => Invoice::whereIn('id', $invoicesId)
|
||||
);
|
||||
}
|
||||
}
|
||||
79
app/Models/Plan.php
Normal file
79
app/Models/Plan.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Classes\Price as PriceClass;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Plan extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'type',
|
||||
'billing_period',
|
||||
'billing_unit',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the available prices of the plan.
|
||||
*/
|
||||
public function prices()
|
||||
{
|
||||
return $this->hasMany(Price::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the priceable model of the plan.
|
||||
*/
|
||||
public function priceable()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the price of the plan.
|
||||
*/
|
||||
public function price()
|
||||
{
|
||||
if ($this->type === 'free') {
|
||||
return new PriceClass(['currency' => Currency::find(session('currency', config('settings.default_currency')))], free: true);
|
||||
}
|
||||
$currency = session('currency', config('settings.default_currency'));
|
||||
$price = $this->prices->where('currency_code', $currency)->first();
|
||||
|
||||
return new PriceClass((object) [
|
||||
'price' => $price,
|
||||
'setup_fee' => $price->setup_fee,
|
||||
'currency' => $price->currency,
|
||||
]);
|
||||
}
|
||||
|
||||
// Time between billing periods
|
||||
public function billingDuration(): Attribute
|
||||
{
|
||||
if ($this->type === 'free' || $this->type == 'one-time') {
|
||||
return Attribute::make(get: fn () => 0);
|
||||
}
|
||||
$diffInDays = match ($this->billing_unit) {
|
||||
'day' => 1,
|
||||
'week' => 7,
|
||||
'month' => 30,
|
||||
'year' => 365,
|
||||
};
|
||||
|
||||
return Attribute::make(
|
||||
get: fn () => $diffInDays * $this->billing_period
|
||||
);
|
||||
}
|
||||
|
||||
public function services()
|
||||
{
|
||||
return $this->hasMany(Service::class);
|
||||
}
|
||||
}
|
||||
32
app/Models/Price.php
Normal file
32
app/Models/Price.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class Price extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function priceable(): MorphTo
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function currency(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Currency::class);
|
||||
}
|
||||
|
||||
public function plan(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Plan::class);
|
||||
}
|
||||
}
|
||||
81
app/Models/Product.php
Normal file
81
app/Models/Product.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Traits\HasPlans;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||
|
||||
class Product extends Model
|
||||
{
|
||||
use HasFactory, HasPlans;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $auditInclude = [
|
||||
'name',
|
||||
'description',
|
||||
'category_id',
|
||||
'enabled',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the category of the product.
|
||||
*/
|
||||
public function category(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configurable options of the product.
|
||||
*/
|
||||
public function configOptions(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(ConfigOption::class, ConfigOptionProduct::class, 'product_id', 'id', 'id', 'config_option_id')->where('hidden', false)->orderBy('sort', 'asc')->orderBy('id', 'desc');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the extension of the product.
|
||||
*/
|
||||
public function server()
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all services using this product.
|
||||
*/
|
||||
public function services(): HasMany
|
||||
{
|
||||
return $this->hasMany(Service::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings of the product.
|
||||
*/
|
||||
public function settings(): MorphMany
|
||||
{
|
||||
return $this->morphMany(Setting::class, 'settingable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available products upgrades
|
||||
*/
|
||||
public function upgrades()
|
||||
{
|
||||
return $this->belongsToMany(Product::class, 'product_upgrades', 'product_id', 'upgrade_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all upgradable config options for the product.
|
||||
*/
|
||||
public function upgradableConfigOptions(): HasManyThrough
|
||||
{
|
||||
return $this->hasManyThrough(ConfigOption::class, ConfigOptionProduct::class, 'product_id', 'id', 'id', 'config_option_id')->where('hidden', false)->where('upgradable', true)->orderBy('sort', 'asc')->orderBy('id', 'desc');
|
||||
}
|
||||
}
|
||||
23
app/Models/ProductUpgrade.php
Normal file
23
app/Models/ProductUpgrade.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ProductUpgrade extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
|
||||
public function upgrade()
|
||||
{
|
||||
return $this->belongsTo(Product::class, 'upgrade_id');
|
||||
}
|
||||
}
|
||||
26
app/Models/Property.php
Normal file
26
app/Models/Property.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Observers\PropertyObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[ObservedBy([PropertyObserver::class])]
|
||||
class Property extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $guarded = [];
|
||||
|
||||
public function parent_property()
|
||||
{
|
||||
return $this->belongsTo(CustomProperty::class, 'custom_property_id');
|
||||
}
|
||||
|
||||
public function model()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
}
|
||||
35
app/Models/Role.php
Normal file
35
app/Models/Role.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Role extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'permissions',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'permissions' => 'array',
|
||||
];
|
||||
|
||||
public function users()
|
||||
{
|
||||
return $this->belongsToMany(User::class);
|
||||
}
|
||||
}
|
||||
13
app/Models/Server.php
Normal file
13
app/Models/Server.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
class Server extends Extension
|
||||
{
|
||||
protected $table = 'extensions';
|
||||
|
||||
public function newQuery($excludeDeleted = true)
|
||||
{
|
||||
return parent::newQuery($excludeDeleted)->where('type', 'server');
|
||||
}
|
||||
}
|
||||
220
app/Models/Service.php
Normal file
220
app/Models/Service.php
Normal file
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Classes\Price;
|
||||
use App\Models\Traits\HasProperties;
|
||||
use App\Observers\ServiceObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Paymenter\Extensions\Servers\Proxmox\Models\Server as ProxmoxServer;
|
||||
|
||||
#[ObservedBy([ServiceObserver::class])]
|
||||
class Service extends Model
|
||||
{
|
||||
use HasFactory, HasProperties;
|
||||
|
||||
public const STATUS_PENDING = 'pending';
|
||||
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
|
||||
public const STATUS_SUSPENDED = 'suspended';
|
||||
|
||||
protected $fillable = [
|
||||
'order_id',
|
||||
'product_id',
|
||||
'plan_id',
|
||||
'quantity',
|
||||
'price',
|
||||
'expires_at',
|
||||
'subscription_id',
|
||||
'status',
|
||||
'coupon_id',
|
||||
'user_id',
|
||||
'currency_code',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'expires_at' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the order that owns the service.
|
||||
*/
|
||||
public function order()
|
||||
{
|
||||
return $this->belongsTo(Order::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the coupon that owns the service.
|
||||
*/
|
||||
public function coupon()
|
||||
{
|
||||
return $this->belongsTo(Coupon::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currency corresponding to the service.
|
||||
*/
|
||||
public function currency()
|
||||
{
|
||||
return $this->hasOne(Currency::class, 'code', 'currency_code');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that owns the service.
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Price of the service.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formattedPrice(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => new Price(['price' => $this->price * $this->quantity, 'currency' => $this->currency])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description for the next invoice item.
|
||||
*/
|
||||
public function description(): Attribute
|
||||
{
|
||||
if ($this->plan->type == 'free' || $this->plan->type == 'one-time') {
|
||||
return Attribute::make(
|
||||
get: fn () => $this->product->name
|
||||
);
|
||||
}
|
||||
$date = $this->expires_at ?? now();
|
||||
$endDate = $date->copy()->{'add' . ucfirst($this->plan->billing_unit) . 's'}($this->plan->billing_period);
|
||||
|
||||
return Attribute::make(
|
||||
get: fn () => $this->product->name . ' (' . $date->format('M d, Y') . ' - ' . $endDate->format('M d, Y') . ')'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate next due date.
|
||||
*/
|
||||
public function calculateNextDueDate()
|
||||
{
|
||||
if ($this->plan->type == 'one-time' || $this->plan->type == 'free') {
|
||||
return null;
|
||||
}
|
||||
$date = $this->expires_at ?? now();
|
||||
|
||||
return $date->{'add' . ucfirst($this->plan->billing_unit) . 's'}($this->plan->billing_period);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the product corresponding to the service.
|
||||
*/
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plan corresponding to the service.
|
||||
*/
|
||||
public function plan()
|
||||
{
|
||||
return $this->belongsTo(Plan::class);
|
||||
}
|
||||
|
||||
public function proxmoxServer(): HasOne
|
||||
{
|
||||
return $this->hasOne(ProxmoxServer::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the service's configurations.
|
||||
*/
|
||||
public function configs()
|
||||
{
|
||||
return $this->morphMany(ServiceConfig::class, 'configurable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get invoiceItems
|
||||
*/
|
||||
public function invoiceItems()
|
||||
{
|
||||
return $this->morphMany(InvoiceItem::class, 'reference');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get invoices
|
||||
*/
|
||||
public function invoices()
|
||||
{
|
||||
return $this->hasManyThrough(Invoice::class, InvoiceItem::class, 'reference_id', 'id', 'id', 'invoice_id')->where('reference_type', Service::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cancellation requests
|
||||
*/
|
||||
public function cancellation()
|
||||
{
|
||||
return $this->hasOne(ServiceCancellation::class);
|
||||
}
|
||||
|
||||
public function cancellable(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => $this->status !== 'cancelled' && $this->plan->type != 'free' && $this->plan->type != 'one-time' && !$this->cancellation?->exists()
|
||||
);
|
||||
}
|
||||
|
||||
public function upgradable(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => ($this->productUpgrades()->count() > 0 || $this->product->upgradableConfigOptions()->count() > 0) && $this->status == 'active' && $this->upgrade->where('status', ServiceUpgrade::STATUS_PENDING)->count() == 0);
|
||||
}
|
||||
|
||||
public function productUpgrades()
|
||||
{
|
||||
return $this->product->upgrades->filter(function ($product) {
|
||||
$plan = $product->plans()->where('billing_unit', $this->plan->billing_unit)->where('billing_period', $this->plan->billing_period)->get();
|
||||
// Only get the upgrades that have the exact same billing cycle as the service
|
||||
if ($plan->count() > 0) {
|
||||
$product->plan = $plan->first();
|
||||
|
||||
return $product;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public function recalculatePrice()
|
||||
{
|
||||
// Calculate the price based on the plan and quantity and config options
|
||||
$price = $this->plan->price()->price * $this->quantity;
|
||||
$this->configs->each(function ($config) use (&$price) {
|
||||
$configValue = $config->configValue;
|
||||
if ($configValue) {
|
||||
$price += $configValue->price(null, $this->plan->billing_period, $this->plan->billing_unit, $this->currency_code)->price;
|
||||
}
|
||||
});
|
||||
$this->price = $price;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function upgrade()
|
||||
{
|
||||
return $this->hasMany(ServiceUpgrade::class);
|
||||
}
|
||||
}
|
||||
25
app/Models/ServiceCancellation.php
Normal file
25
app/Models/ServiceCancellation.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Observers\ServiceCancellationObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[ObservedBy([ServiceCancellationObserver::class])]
|
||||
class ServiceCancellation extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'service_id',
|
||||
'reason',
|
||||
'type',
|
||||
];
|
||||
|
||||
public function service()
|
||||
{
|
||||
return $this->belongsTo(Service::class);
|
||||
}
|
||||
}
|
||||
41
app/Models/ServiceConfig.php
Normal file
41
app/Models/ServiceConfig.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ServiceConfig extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'service_id',
|
||||
'config_option_id',
|
||||
'config_value_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the service that owns the service config.
|
||||
*/
|
||||
public function service()
|
||||
{
|
||||
return $this->belongsTo(Service::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the config option that owns the service config.
|
||||
*/
|
||||
public function configOption()
|
||||
{
|
||||
return $this->belongsTo(ConfigOption::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the config value that owns the service config.
|
||||
*/
|
||||
public function configValue()
|
||||
{
|
||||
return $this->belongsTo(ConfigOption::class, 'config_value_id');
|
||||
}
|
||||
}
|
||||
108
app/Models/ServiceUpgrade.php
Normal file
108
app/Models/ServiceUpgrade.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Classes\Price;
|
||||
use App\Observers\ServiceUpgradeObserver;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[ObservedBy([ServiceUpgradeObserver::class])]
|
||||
class ServiceUpgrade extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const STATUS_PENDING = 'pending';
|
||||
|
||||
public const STATUS_COMPLETED = 'completed';
|
||||
|
||||
public $guarded = [];
|
||||
|
||||
public function service()
|
||||
{
|
||||
return $this->belongsTo(Service::class);
|
||||
}
|
||||
|
||||
public function product()
|
||||
{
|
||||
return $this->belongsTo(Product::class);
|
||||
}
|
||||
|
||||
public function plan()
|
||||
{
|
||||
return $this->belongsTo(Plan::class);
|
||||
}
|
||||
|
||||
public function invoice()
|
||||
{
|
||||
return $this->belongsTo(Invoice::class);
|
||||
}
|
||||
|
||||
public function configs()
|
||||
{
|
||||
return $this->morphMany(ServiceConfig::class, 'configurable');
|
||||
}
|
||||
|
||||
public function calculateProratedAmount($oldItem, $newItem): Price
|
||||
{
|
||||
// Calculate the total number of days in the billing period
|
||||
$billingPeriodDays = match ($this->service->plan->billing_unit) {
|
||||
'day' => $this->service->plan->billing_period,
|
||||
'week' => $this->service->plan->billing_period * 7,
|
||||
'month' => $this->service->plan->billing_period * 30,
|
||||
'year' => $this->service->plan->billing_period * 365,
|
||||
default => 0,
|
||||
};
|
||||
|
||||
// Calculate the remaining days until the service expires
|
||||
$expiresAt = $this->service->expires_at->copy()->startOfDay();
|
||||
$now = Carbon::now()->startOfDay();
|
||||
$remainingDays = $expiresAt->diffInDays($now, true);
|
||||
$remainingDays = min($remainingDays, $billingPeriodDays);
|
||||
|
||||
// Calculate the prorated amount
|
||||
$newPrice = $newItem->price(null, $this->service->plan->billing_period, $this->service->plan->billing_unit, $this->service->currency_code);
|
||||
if (empty($oldItem)) {
|
||||
$oldPrice = 0;
|
||||
} else {
|
||||
$oldPrice = $oldItem->price(null, $this->service->plan->billing_period, $this->service->plan->billing_unit, $this->service->currency_code)->price;
|
||||
}
|
||||
$priceDifference = $newPrice->price - $oldPrice;
|
||||
$total = ($priceDifference / $billingPeriodDays) * $remainingDays;
|
||||
|
||||
return new Price([
|
||||
'price' => $total,
|
||||
'currency' => $this->service->currency,
|
||||
]);
|
||||
}
|
||||
|
||||
public function calculatePrice(): Price
|
||||
{
|
||||
$total = $this->calculateProratedAmount(
|
||||
$this->service->product,
|
||||
$this->product
|
||||
)->price;
|
||||
|
||||
foreach ($this->configs as $config) {
|
||||
$configValue = $config->configValue;
|
||||
if (!$configValue) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oldPrice = $this->service->configs->where('config_option_id', $config->config_option_id)->first();
|
||||
|
||||
$ctotal = $this->calculateProratedAmount(
|
||||
$oldPrice ? $oldPrice->configValue : null,
|
||||
$configValue
|
||||
);
|
||||
$total += $ctotal->price;
|
||||
}
|
||||
|
||||
return new Price([
|
||||
'price' => $total,
|
||||
'currency' => $currency ?? $this->service->currency,
|
||||
]);
|
||||
}
|
||||
}
|
||||
109
app/Models/Session.php
Normal file
109
app/Models/Session.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Session as FacadesSession;
|
||||
|
||||
class Session extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'user_agent',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'last_activity' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function impersonating(): bool
|
||||
{
|
||||
try {
|
||||
$payload = $this->payload;
|
||||
|
||||
$decoded = config('session.encrypt')
|
||||
? Crypt::decryptString($payload)
|
||||
: base64_decode($payload);
|
||||
|
||||
$data = unserialize($decoded);
|
||||
|
||||
return !empty($data['impersonating']);
|
||||
} catch (\Throwable $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function getIsCurrentDeviceAttribute()
|
||||
{
|
||||
return $this->id === FacadesSession::getId();
|
||||
}
|
||||
|
||||
public function getFormattedLastActiveAttribute()
|
||||
{
|
||||
return $this->last_activity->diffForHumans();
|
||||
}
|
||||
|
||||
public function getIsMobileAttribute()
|
||||
{
|
||||
return preg_match('/(android|webos|avantgo|blackberry|bolt|boost|cricket|docomo|fone|hiptop|mini|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i', $this->user_agent);
|
||||
}
|
||||
|
||||
public function getFormattedDeviceAttribute()
|
||||
{
|
||||
if (preg_match('/Linux/i', $this->user_agent)) {
|
||||
$os = 'Linux';
|
||||
} elseif (preg_match('/Mac/i', $this->user_agent)) {
|
||||
$os = 'Mac';
|
||||
} elseif (preg_match('/iPhone/i', $this->user_agent)) {
|
||||
$os = 'iPhone';
|
||||
} elseif (preg_match('/iPad/i', $this->user_agent)) {
|
||||
$os = 'iPad';
|
||||
} elseif (preg_match('/Droid/i', $this->user_agent)) {
|
||||
$os = 'Droid';
|
||||
} elseif (preg_match('/Unix/i', $this->user_agent)) {
|
||||
$os = 'Unix';
|
||||
} elseif (preg_match('/Windows/i', $this->user_agent)) {
|
||||
$os = 'Windows';
|
||||
} else {
|
||||
$os = 'Unknown';
|
||||
}
|
||||
|
||||
// Browser Detection
|
||||
|
||||
if (preg_match('/Firefox/i', $this->user_agent)) {
|
||||
$br = 'Firefox';
|
||||
} elseif (preg_match('/Mac/i', $this->user_agent)) {
|
||||
$br = 'Mac';
|
||||
} elseif (preg_match('/Chrome/i', $this->user_agent)) {
|
||||
$br = 'Chrome';
|
||||
} elseif (preg_match('/Opera/i', $this->user_agent)) {
|
||||
$br = 'Opera';
|
||||
} elseif (preg_match('/MSIE/i', $this->user_agent)) {
|
||||
$br = 'IE';
|
||||
} else {
|
||||
$br = 'Unknown';
|
||||
}
|
||||
|
||||
return "{$os} - {$br}";
|
||||
}
|
||||
}
|
||||
32
app/Models/Setting.php
Normal file
32
app/Models/Setting.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Events\Setting\Retrieved;
|
||||
use App\Events\Setting\Saved;
|
||||
use App\Events\Setting\Saving;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Setting extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'key',
|
||||
'value',
|
||||
'type',
|
||||
'encrypted',
|
||||
];
|
||||
|
||||
protected $dispatchesEvents = [
|
||||
'retrieved' => Retrieved::class,
|
||||
'saving' => Saving::class,
|
||||
'saved' => Saved::class,
|
||||
];
|
||||
}
|
||||
13
app/Models/TaxRate.php
Normal file
13
app/Models/TaxRate.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class TaxRate extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = ['name', 'rate', 'country'];
|
||||
}
|
||||
45
app/Models/Ticket.php
Normal file
45
app/Models/Ticket.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Observers\TicketObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[ObservedBy([TicketObserver::class])]
|
||||
|
||||
class Ticket extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'subject',
|
||||
'status',
|
||||
'priority',
|
||||
'department',
|
||||
'user_id',
|
||||
'assigned_to',
|
||||
'service_id',
|
||||
];
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function assignedTo()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'assigned_to');
|
||||
}
|
||||
|
||||
public function service()
|
||||
{
|
||||
return $this->belongsTo(Service::class);
|
||||
}
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return $this->hasMany(TicketMessage::class);
|
||||
}
|
||||
}
|
||||
30
app/Models/TicketMessage.php
Normal file
30
app/Models/TicketMessage.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Observers\TicketMessageObserver;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
#[ObservedBy([TicketMessageObserver::class])]
|
||||
class TicketMessage extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'ticket_id',
|
||||
'user_id',
|
||||
'message',
|
||||
];
|
||||
|
||||
public function ticket()
|
||||
{
|
||||
return $this->belongsTo(Ticket::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
79
app/Models/Traits/HasPlans.php
Normal file
79
app/Models/Traits/HasPlans.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Traits;
|
||||
|
||||
use App\Classes\Price;
|
||||
use App\Models\Plan;
|
||||
|
||||
trait HasPlans
|
||||
{
|
||||
/**
|
||||
* Get the plans (prices) of the product.
|
||||
*/
|
||||
public function plans()
|
||||
{
|
||||
return $this->morphMany(Plan::class, 'priceable')->orderBy('sort');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available plans of the product.
|
||||
*/
|
||||
public function availablePlans($currency = null)
|
||||
{
|
||||
$currency = $currency ?? session('currency', config('settings.default_currency'));
|
||||
|
||||
return $this->plans->filter(function ($plan) use ($currency) {
|
||||
if ($plan->type === 'free') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $plan->prices->when($currency, function ($query) use ($currency) {
|
||||
// Or where plan is free
|
||||
return $query->where('currency_code', $currency);
|
||||
})->isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first price of the plan.
|
||||
*/
|
||||
public function price($plan_id = null, $billing_period = null, $billing_unit = null, $currency = null)
|
||||
{
|
||||
$priceAndCurrency = [
|
||||
'price' => null,
|
||||
'currency' => null,
|
||||
];
|
||||
|
||||
// Check for free plan
|
||||
if ($this->availablePlans()->where('type', 'free')->isNotEmpty()) {
|
||||
return new Price(free: true, dontShowUnavailablePrice: $this->dontShowUnavailablePrice ?? false);
|
||||
}
|
||||
|
||||
// If plan_id is not provided, and billing_period is provided, get the first plan with the billing period and time interval
|
||||
if (!$plan_id && $billing_period && $billing_unit) {
|
||||
$plan = $this->availablePlans()->where('billing_period', $billing_period)->where('billing_unit', $billing_unit)->first();
|
||||
$plan_id = $plan->id ?? null;
|
||||
}
|
||||
|
||||
$currency = $currency ?? session('currency', config('settings.default_currency'));
|
||||
|
||||
foreach ($this->availablePlans()->when($plan_id, function ($query) use ($plan_id) {
|
||||
return $query->where('id', $plan_id);
|
||||
}) as $plan) {
|
||||
foreach ($plan->prices->when($currency, function ($query) use ($currency) {
|
||||
return $query->where('currency_code', $currency);
|
||||
}) as $price) {
|
||||
if ($price->price < $priceAndCurrency['price'] || $priceAndCurrency['price'] === null) {
|
||||
$priceAndCurrency['price'] = $price;
|
||||
$priceAndCurrency['currency'] = $price->currency;
|
||||
}
|
||||
}
|
||||
|
||||
if ($priceAndCurrency['price']) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Price($priceAndCurrency, dontShowUnavailablePrice: $this->dontShowUnavailablePrice ?? false);
|
||||
}
|
||||
}
|
||||
16
app/Models/Traits/HasProperties.php
Normal file
16
app/Models/Traits/HasProperties.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Traits;
|
||||
|
||||
use App\Models\Property;
|
||||
|
||||
trait HasProperties
|
||||
{
|
||||
/**
|
||||
* Get the properties of the model which has this trait
|
||||
*/
|
||||
public function properties()
|
||||
{
|
||||
return $this->morphMany(Property::class, 'model');
|
||||
}
|
||||
}
|
||||
8
app/Models/Traits/Settingable.php
Normal file
8
app/Models/Traits/Settingable.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Traits;
|
||||
|
||||
trait Settingable
|
||||
{
|
||||
//
|
||||
}
|
||||
195
app/Models/User.php
Normal file
195
app/Models/User.php
Normal file
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
|
||||
use App\Models\Traits\HasProperties;
|
||||
use App\Observers\UserObserver;
|
||||
use Dedoc\Scramble\Attributes\SchemaName;
|
||||
use Filament\Models\Contracts\FilamentUser;
|
||||
use Filament\Models\Contracts\HasAvatar;
|
||||
use Filament\Panel;
|
||||
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Passport\HasApiTokens;
|
||||
|
||||
#[SchemaName('UserModel')]
|
||||
#[ObservedBy([UserObserver::class])]
|
||||
class User extends Authenticatable implements FilamentUser, HasAvatar
|
||||
{
|
||||
use HasApiTokens, HasFactory, HasProperties, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'first_name',
|
||||
'last_name',
|
||||
'email',
|
||||
'password',
|
||||
'role_id',
|
||||
'tfa_secret',
|
||||
'email_verified_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
'tfa_secret',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'tfa_secret' => 'encrypted',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initials of the user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function initials(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => strtoupper(substr($this->first_name, 0, 1) . substr($this->last_name, 0, 1)),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Avatar URL for the user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function avatar(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => 'https://www.gravatar.com/avatar/' . md5(strtolower($this->email)) . '?d=' . urlencode((string) config('settings.gravatar_default')),
|
||||
);
|
||||
}
|
||||
|
||||
public function getFilamentAvatarUrl(): ?string
|
||||
{
|
||||
return $this->avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the display name for the user.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => ($this->first_name . ' ' . $this->last_name) ?: $this->email,
|
||||
);
|
||||
}
|
||||
|
||||
public function hasPermission($permission): bool
|
||||
{
|
||||
if (is_null($this->role)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the user has all permissions, return true
|
||||
if (in_array('*', $this->role->permissions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($permission, $this->role->permissions);
|
||||
}
|
||||
|
||||
/** Relationships */
|
||||
/**
|
||||
* Get the role that the user belongs to.
|
||||
* Can be null if the user is a normal user (non-admin).
|
||||
*/
|
||||
public function role()
|
||||
{
|
||||
return $this->belongsTo(Role::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's sessions.
|
||||
*/
|
||||
public function sessions()
|
||||
{
|
||||
return $this->hasMany(Session::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's orders.
|
||||
*/
|
||||
public function orders()
|
||||
{
|
||||
return $this->hasMany(Order::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's services
|
||||
*/
|
||||
public function services()
|
||||
{
|
||||
return $this->hasMany(Service::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's audit logs.
|
||||
*/
|
||||
public function audits()
|
||||
{
|
||||
return $this->morphMany(AuditLog::class, 'model');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's invoices.
|
||||
*/
|
||||
public function invoices()
|
||||
{
|
||||
return $this->hasMany(Invoice::class);
|
||||
}
|
||||
|
||||
public function canAccessPanel(Panel $panel): bool
|
||||
{
|
||||
if ($panel->getId() === 'admin') {
|
||||
return !is_null($this->role);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user tickets
|
||||
*/
|
||||
public function tickets()
|
||||
{
|
||||
return $this->hasMany(Ticket::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user's credits
|
||||
*/
|
||||
public function credits()
|
||||
{
|
||||
return $this->hasMany(Credit::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user