'GHS', ]; protected $casts = [ 'price_monthly' => 'decimal:2', 'price_quarterly' => 'decimal:2', 'price_yearly' => 'decimal:2', 'price_biennial' => 'decimal:2', 'setup_fee' => 'decimal:2', 'disk_gb' => 'integer', 'bandwidth_gb' => 'integer', 'cpu_cores' => 'integer', 'ram_mb' => 'integer', 'max_domains' => 'integer', 'max_databases' => 'integer', 'max_email_accounts' => 'integer', 'max_ftp_accounts' => 'integer', 'ssl_included' => 'boolean', 'backups_included' => 'boolean', 'backup_retention_days' => 'integer', 'features' => 'array', 'php_versions' => 'array', 'is_active' => 'boolean', 'is_featured' => 'boolean', 'is_visible' => 'boolean', 'sort_order' => 'integer', ]; public function orders(): HasMany { return $this->hasMany(CustomerHostingOrder::class); } public function scopeActive($query) { return $query->where('is_active', true); } public function scopeVisible($query) { return $query->where('is_visible', true); } public function scopeOfCategory($query, string $category) { return $query->where('category', $category); } public function scopeOfType($query, string $type) { return $query->where('type', $type); } public function scopeSharedHosting($query) { return $query->where('category', self::CATEGORY_SHARED); } public function scopeServers($query) { return $query->whereIn('category', [self::CATEGORY_VPS, self::CATEGORY_DEDICATED]); } public function scopeOrdered($query) { return $query->orderBy('sort_order')->orderBy('price_monthly'); } public function isSharedHosting(): bool { return $this->category === self::CATEGORY_SHARED; } public function isVps(): bool { return $this->category === self::CATEGORY_VPS; } public function isDedicated(): bool { return $this->category === self::CATEGORY_DEDICATED; } public function requiresContabo(): bool { return in_array($this->category, [self::CATEGORY_VPS, self::CATEGORY_DEDICATED]); } public function isActive(): bool { return (bool) $this->is_active; } public function isVisible(): bool { return (bool) $this->is_visible; } public function getPriceForCycle(string $cycle): ?float { // Use dynamic pricing for VPS/Dedicated if ($this->requiresContabo()) { // Contabo VPS does not offer quarterly billing if ($this->isVps() && $cycle === 'quarterly') { return null; } $pricing = $this->getDynamicPricing(); return match ($cycle) { 'monthly' => $pricing['monthly'], 'quarterly' => $pricing['quarterly'], 'semiannual' => $pricing['semiannual'], 'yearly' => $pricing['yearly'], default => null, }; } return match ($cycle) { 'monthly' => (float) $this->price_monthly, 'quarterly' => (float) $this->price_quarterly, 'semiannual' => null, 'yearly' => (float) $this->price_yearly, 'biennial' => (float) $this->price_biennial, default => null, }; } public function getDynamicPricing(): array { $pricingService = app(HostingPricingService::class); return $pricingService->getDynamicPriceForProduct($this); } public function getPricingBreakdown(): array { $pricingService = app(HostingPricingService::class); return $pricingService->getPricingBreakdown($this); } public function getDisplayPriceMonthlyAttribute(): float { if ($this->requiresContabo()) { return $this->getDynamicPricing()['monthly']; } return (float) $this->price_monthly; } public function getDisplayPriceYearlyAttribute(): float { if ($this->requiresContabo()) { return $this->getDynamicPricing()['yearly']; } return (float) $this->price_yearly; } public function getDisplayCurrencyAttribute(): string { return 'GHS'; } public function getYearlyDiscount(): ?float { $monthlyPrice = $this->display_price_monthly; $yearlyPrice = $this->display_price_yearly; if (!$yearlyPrice || !$monthlyPrice) { return null; } $monthlyTotal = $monthlyPrice * 12; return round((($monthlyTotal - $yearlyPrice) / $monthlyTotal) * 100, 1); } public function catalogSummary(): ?string { $parts = []; if ($this->disk_gb !== null) { $parts[] = $this->formatDiskSize(); } $domainLimit = $this->formatDomainLimit(); if ($domainLimit !== null) { $parts[] = $domainLimit; } return $parts !== [] ? implode(' ยท ', $parts) : null; } public function formatDiskSize(): string { if (!$this->disk_gb) { return 'Unlimited'; } if ($this->disk_gb >= 1000) { return round($this->disk_gb / 1000, 1) . ' TB'; } return $this->disk_gb . ' GB'; } public function formatRam(): string { if (!$this->ram_mb) { return '-'; } if ($this->ram_mb >= 1024) { return round($this->ram_mb / 1024, 1) . ' GB'; } return $this->ram_mb . ' MB'; } public function formatBandwidth(): string { if (!$this->bandwidth_gb) { return 'Unlimited'; } if ($this->bandwidth_gb >= 1000) { return round($this->bandwidth_gb / 1000, 1) . ' TB'; } return $this->bandwidth_gb . ' GB'; } public function formatDomainLimit(): ?string { if ($this->max_domains === null) { return null; } if ($this->max_domains === -1) { return 'Unlimited sites'; } return $this->max_domains.' site'.($this->max_domains === 1 ? '' : 's'); } public function preferredNodeSegment(): ?string { if (! $this->isSharedHosting()) { return null; } return match ($this->type) { self::TYPE_WORDPRESS => HostingNode::SEGMENT_WORDPRESS, self::TYPE_MULTI_DOMAIN => HostingNode::SEGMENT_HIGH_RESOURCE, self::TYPE_SINGLE_DOMAIN => HostingNode::SEGMENT_STARTER, default => HostingNode::SEGMENT_GENERAL, }; } }