Add all files
This commit is contained in:
45
app/Livewire/Services/Cancel.php
Normal file
45
app/Livewire/Services/Cancel.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Services;
|
||||
|
||||
use App\Livewire\Component;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceCancellation;
|
||||
use Livewire\Attributes\Validate;
|
||||
|
||||
class Cancel extends Component
|
||||
{
|
||||
public Service $service;
|
||||
|
||||
#[Validate('required|in:end_of_period,immediate')]
|
||||
public $type = 'end_of_period';
|
||||
|
||||
#[Validate('required')]
|
||||
public $reason = '';
|
||||
|
||||
public function cancelService()
|
||||
{
|
||||
$this->authorize('view', $this->service);
|
||||
|
||||
$this->validate();
|
||||
|
||||
// Event hook will handle the cancellation (if its immediate or end of period)
|
||||
ServiceCancellation::create([
|
||||
'service_id' => $this->service->id,
|
||||
'type' => $this->type,
|
||||
'reason' => $this->reason,
|
||||
]);
|
||||
|
||||
$this->notify(__('services.cancellation_requested'), 'success');
|
||||
|
||||
$this->redirect(route('services.show', $this->service), true);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('services.cancel')->layoutData([
|
||||
'title' => __('services.cancellation', ['service' => $this->service->product->name]),
|
||||
'sidebar' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
37
app/Livewire/Services/Index.php
Normal file
37
app/Livewire/Services/Index.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Services;
|
||||
|
||||
use App\Livewire\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public $status = null;
|
||||
|
||||
public function render()
|
||||
{
|
||||
$query = Auth::user()->services()
|
||||
->with([
|
||||
'product.category',
|
||||
'product.server',
|
||||
'plan',
|
||||
'proxmoxServer',
|
||||
])
|
||||
->orderBy('created_at', 'desc');
|
||||
|
||||
if ($this->status) {
|
||||
$query->where('status', $this->status);
|
||||
}
|
||||
|
||||
return view('services.index', [
|
||||
'services' => $query->paginate(config('settings.pagination')),
|
||||
])->layoutData([
|
||||
'title' => 'Services',
|
||||
'sidebar' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
117
app/Livewire/Services/Show.php
Normal file
117
app/Livewire/Services/Show.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Services;
|
||||
|
||||
use App\Helpers\ExtensionHelper;
|
||||
use App\Livewire\Component;
|
||||
use App\Models\Service;
|
||||
use Paymenter\Extensions\Servers\Proxmox\Models\Server as ProxmoxServer;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Url;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
public Service $service;
|
||||
|
||||
public ?ProxmoxServer $proxmoxServer = null;
|
||||
|
||||
#[Locked]
|
||||
public $buttons = [];
|
||||
|
||||
#[Locked]
|
||||
public $views = [];
|
||||
|
||||
#[Url('tab', except: false), Locked]
|
||||
public $currentView;
|
||||
|
||||
#[Url('cancel', except: false)]
|
||||
public bool $showCancel = false;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
// Attach Proxmox server (if any) so we can show hostname on the service page
|
||||
$this->proxmoxServer = ProxmoxServer::where('service_id', $this->service->id)->first();
|
||||
|
||||
// Only fetch the actions if the service is active
|
||||
if ($this->service->status == Service::STATUS_ACTIVE) {
|
||||
$actions = [];
|
||||
try {
|
||||
$actions = ExtensionHelper::getActions($this->service);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
// separate the actions into buttons and views
|
||||
foreach ($actions as $action) {
|
||||
if ($action['type'] == 'button') {
|
||||
$this->buttons[] = $action;
|
||||
} elseif ($action['type'] == 'view') {
|
||||
$this->views[] = $action;
|
||||
}
|
||||
}
|
||||
$this->currentView = $this->currentView ?? ($this->views[0]['name'] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
public function changeView($view)
|
||||
{
|
||||
if (!$view) {
|
||||
return;
|
||||
}
|
||||
if ($this->currentView === $view || !in_array($view, array_column($this->views, 'name'))) {
|
||||
return $this->skipRender();
|
||||
}
|
||||
$this->currentView = $view;
|
||||
}
|
||||
|
||||
public function updatedShowCancel($value)
|
||||
{
|
||||
if (!$this->service->cancellable) {
|
||||
$this->notify('This service cannot be cancelled', 'error');
|
||||
$this->showCancel = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function goto($function)
|
||||
{
|
||||
// Check if function is allowed
|
||||
if (!in_array($function, array_column($this->buttons, 'function'))) {
|
||||
$this->notify('This action is not allowed', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
$result = ExtensionHelper::callService($this->service, $function);
|
||||
// If its a response, return it
|
||||
if (!is_string($result)) {
|
||||
return $result;
|
||||
}
|
||||
$this->redirect($result);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$view = null;
|
||||
$previousView = $this->currentView;
|
||||
|
||||
if ($this->currentView) {
|
||||
try {
|
||||
// Search array for the current view
|
||||
$currentViewObj = $this->views[array_search($this->currentView, array_column($this->views, 'name'))] ?? null;
|
||||
if (!$currentViewObj) {
|
||||
throw new \Exception('View not found');
|
||||
}
|
||||
$view = ExtensionHelper::getView($this->service, $currentViewObj);
|
||||
} catch (\Exception $e) {
|
||||
if ($previousView !== $this->views[0]['name'] ?? null) {
|
||||
$this->notify('Got an error while trying to load the view', 'error');
|
||||
}
|
||||
$this->currentView = $this->views[0]['name'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return view('services.show', ['extensionView' => $view])->layoutData([
|
||||
'title' => 'Services',
|
||||
'sidebar' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
263
app/Livewire/Services/Upgrade.php
Normal file
263
app/Livewire/Services/Upgrade.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Services;
|
||||
|
||||
use App\Classes\Price;
|
||||
use App\Events\Invoice\Created as InvoiceCreated;
|
||||
use App\Jobs\Server\UpgradeJob;
|
||||
use App\Livewire\Component;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Product;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceUpgrade;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Attributes\Computed;
|
||||
|
||||
class Upgrade extends Component
|
||||
{
|
||||
public Service $service;
|
||||
|
||||
public $upgrade;
|
||||
|
||||
public Product $upgradeProduct;
|
||||
|
||||
public int $step = 1;
|
||||
|
||||
public $configOptions = [];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->authorize('view', $this->service);
|
||||
|
||||
if (!$this->service->upgradable) {
|
||||
$this->notify('This service is not upgradable.', 'error');
|
||||
|
||||
return $this->redirect(route('services.show', $this->service), true);
|
||||
}
|
||||
$this->upgradeProduct = $this->service->product;
|
||||
$this->upgrade = $this->service->product->id;
|
||||
$this->totalToday();
|
||||
|
||||
// We only have upgrabble config options if the product has any
|
||||
if ($this->service->productUpgrades()->count() === 0) {
|
||||
$this->nextStep();
|
||||
}
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function totalToday()
|
||||
{
|
||||
$upgrade = new ServiceUpgrade([
|
||||
'service' => $this->service,
|
||||
'product' => $this->upgradeProduct,
|
||||
]);
|
||||
|
||||
$total = $upgrade->calculateProratedAmount(
|
||||
$this->service->product,
|
||||
$this->upgradeProduct
|
||||
)->price;
|
||||
|
||||
// Calculate prices for config options
|
||||
foreach ($this->configOptions as $optionId => $value) {
|
||||
$option = $this->upgradeProduct->upgradableConfigOptions->where('id', $optionId)->first();
|
||||
if (!$option || !$option->children->contains('id', $value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oldPrice = $this->service->configs->where('config_option_id', $optionId)->first();
|
||||
|
||||
$ctotal = $upgrade->calculateProratedAmount(
|
||||
$oldPrice ? $oldPrice->configValue : null,
|
||||
$option->children->find($value)
|
||||
);
|
||||
$total += $ctotal->price;
|
||||
}
|
||||
|
||||
return new Price([
|
||||
'price' => $total,
|
||||
'currency' => $this->service->currency,
|
||||
]);
|
||||
}
|
||||
|
||||
// When upgrade changes, update the upgradeProduct
|
||||
public function updatedUpgrade($upgrade)
|
||||
{
|
||||
// Check if the upgrade is valid
|
||||
if (!$this->service->productUpgrades()->contains($upgrade) && $upgrade != $this->service->product_id) {
|
||||
$this->notify('Invalid upgrade.', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
$this->upgradeProduct = Product::findOrFail($upgrade);
|
||||
}
|
||||
|
||||
public function nextStep()
|
||||
{
|
||||
$currentConfigOptions = $this->service->configs->pluck('config_value_id', 'config_option_id')->toArray();
|
||||
$this->configOptions = $this->upgradeProduct->upgradableConfigOptions->mapWithKeys(function ($option) use ($currentConfigOptions) {
|
||||
return [
|
||||
$option->id => $currentConfigOptions[$option->id]
|
||||
?? $this->configOptions[$option->id]
|
||||
?? $option->children->first()->id,
|
||||
];
|
||||
})->toArray();
|
||||
|
||||
$this->step++;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
'upgradeProduct.id' => [
|
||||
'required',
|
||||
function ($attribute, $value, $fail) {
|
||||
$plan = $this->upgradeProduct->availablePlans()
|
||||
->where('billing_period', $this->service->plan->billing_period)
|
||||
->where('billing_unit', $this->service->plan->billing_unit)
|
||||
->first();
|
||||
if (!$plan) {
|
||||
$fail(__('Invalid upgrade.'));
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
];
|
||||
foreach ($this->upgradeProduct->upgradableConfigOptions as $option) {
|
||||
if (in_array($option->type, ['text', 'number'])) {
|
||||
$rules["configOptions.{$option->id}"] = ['required'];
|
||||
} elseif ($option->type === 'checkbox') {
|
||||
} else {
|
||||
$rules["configOptions.{$option->id}"] = ['required', 'exists:config_options,id'];
|
||||
}
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function doUpgrade()
|
||||
{
|
||||
if (!$this->service->upgradable) {
|
||||
$this->notify('This service is not upgradable.', 'error');
|
||||
|
||||
return $this->redirect(route('services.show', $this->service), true);
|
||||
}
|
||||
|
||||
$this->validate();
|
||||
|
||||
$upgradePlan = $this->upgradeProduct->availablePlans()->where('billing_period', $this->service->plan->billing_period)->where('billing_unit', $this->service->plan->billing_unit)->first();
|
||||
|
||||
// The old config options must be in upgradableConfigOptions
|
||||
$serviceConfigOptions = $this->service->configs->pluck('config_value_id', 'config_option_id')->toArray();
|
||||
$configOptions = collect($serviceConfigOptions)->filter(function ($value, $key) use ($serviceConfigOptions) {
|
||||
return isset($serviceConfigOptions[$key]) && $this->upgradeProduct->upgradableConfigOptions->contains('id', $key);
|
||||
})->toArray();
|
||||
|
||||
// If the product did not change and the config options are the same, present the user with a message
|
||||
if ($this->upgradeProduct->id === $this->service->product_id && $this->configOptions == $configOptions) {
|
||||
$this->notify('You have not changed anything. Please select a different product or change the configuration options.', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$upgrade = new ServiceUpgrade([
|
||||
'service_id' => $this->service->id,
|
||||
'product_id' => $this->upgradeProduct->id,
|
||||
'plan_id' => $upgradePlan->id,
|
||||
]);
|
||||
$upgrade->save();
|
||||
|
||||
if ($this->configOptions) {
|
||||
foreach ($this->configOptions as $optionId => $value) {
|
||||
$upgrade->configs()->create([
|
||||
'config_option_id' => $optionId,
|
||||
'config_value_id' => $value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$price = $upgrade->calculatePrice();
|
||||
|
||||
if ($price->price <= 0) {
|
||||
$upgrade->status = ServiceUpgrade::STATUS_COMPLETED;
|
||||
$upgrade->save();
|
||||
|
||||
$upgrade->service()->update([
|
||||
'plan_id' => $upgrade->plan_id,
|
||||
'product_id' => $upgrade->product_id,
|
||||
]);
|
||||
|
||||
// Update the service configs
|
||||
foreach ($upgrade->configs as $config) {
|
||||
$upgrade->service->configs()->updateOrCreate(
|
||||
['config_option_id' => $config->config_option_id],
|
||||
['config_value_id' => $config->config_value_id]
|
||||
);
|
||||
}
|
||||
|
||||
$this->service->refresh();
|
||||
|
||||
$this->service->recalculatePrice();
|
||||
|
||||
if ($this->service->product->server) {
|
||||
// If the service has a server, dispatch the upgrade job
|
||||
UpgradeJob::dispatch($this->service);
|
||||
}
|
||||
|
||||
// Check if user has credits in this currency
|
||||
/** @var \App\Models\User */
|
||||
$user = Auth::user();
|
||||
$credit = $user->credits()->where('currency_code', $price->currency->code)->first();
|
||||
|
||||
if ($credit) {
|
||||
// Increment the credits, `abs()` ensures the amount to add is positive
|
||||
$credit->increment('amount', abs($price->price));
|
||||
} else {
|
||||
$user->credits()->create([
|
||||
'currency_code' => $price->currency->code,
|
||||
'amount' => $price->price,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($price->price < 0) {
|
||||
$this->notify('The upgrade has been completed. We\'ve added the remaining amount to your account balance.', 'success');
|
||||
} else {
|
||||
$this->notify('The upgrade has been completed.', 'success');
|
||||
}
|
||||
|
||||
return $this->redirect(route('services.show', $this->service), true);
|
||||
}
|
||||
|
||||
$invoice = new Invoice([
|
||||
'currency_code' => $this->service->currency_code,
|
||||
'status' => Invoice::STATUS_PENDING,
|
||||
'due_at' => Carbon::now()->addDays(7),
|
||||
'user_id' => $this->service->user_id,
|
||||
]);
|
||||
$invoice->save();
|
||||
|
||||
$upgrade->invoice_id = $invoice->id;
|
||||
$upgrade->save();
|
||||
|
||||
$invoice->items()->create([
|
||||
'description' => 'Upgrade ' . $this->service->product->name . ' to ' . $this->upgradeProduct->name,
|
||||
'price' => $price->price,
|
||||
'quantity' => 1,
|
||||
'reference_id' => $upgrade->id,
|
||||
'reference_type' => ServiceUpgrade::class,
|
||||
]);
|
||||
|
||||
event(new InvoiceCreated($invoice));
|
||||
|
||||
$this->notify('The upgrade has been added to your cart. Please complete the payment to proceed.', 'success');
|
||||
|
||||
return $this->redirect(route('invoices.show', $invoice));
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('services.upgrade')->layoutData([
|
||||
'title' => 'Upgrade Service',
|
||||
'sidebar' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
36
app/Livewire/Services/Widget.php
Normal file
36
app/Livewire/Services/Widget.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Services;
|
||||
|
||||
use App\Livewire\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class Widget extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public $status = null;
|
||||
public $limit = null;
|
||||
|
||||
public function render()
|
||||
{
|
||||
$query = Auth::user()->services()->orderByDesc('created_at');
|
||||
|
||||
if ($this->status) {
|
||||
$query->where('status', $this->status);
|
||||
} else {
|
||||
$query->where('status', '!=', 'cancelled');
|
||||
}
|
||||
|
||||
$services = $this->limit
|
||||
? $query->take($this->limit)->get()
|
||||
: $query->paginate(config('settings.pagination'));
|
||||
|
||||
return view('services.widget', [
|
||||
'services' => $services,
|
||||
])->layoutData([
|
||||
'title' => 'Services',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user