Files
Virtnet.bond/app/Livewire/Services/Upgrade.php

262 lines
9.1 KiB
PHP

<?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.'));
return;
}
// Prevent downgrades - only allow upgrades where new price >= current price
$currentPrice = $this->service->plan->price()->price;
$newPrice = $plan->price()->price;
if ($newPrice < $currentPrice) {
$fail(__('Downgrades are not allowed. You may only upgrade to a higher-priced plan.'));
}
},
],
];
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();
// Additional check to prevent downgrades
if ($upgradePlan && $upgradePlan->price()->price < $this->service->plan->price()->price) {
$this->notify('Downgrades are not allowed. You may only upgrade to a higher-priced plan.', 'error');
return;
}
// 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();
// Only process free upgrades immediately (price == 0)
// Negative prices (downgrades) are blocked by validation
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);
}
$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,
]);
}
}