Files
zemailnator/app/Livewire/Dashboard/Pricing.php
idevakk 72b8109a3a feat: enhance pricing page with feature limits and trial management
- Add comprehensive feature limits enforcement middleware
   - Implement subscription dashboard with usage analytics
   - Create reusable plan card component with feature badges
   - Add trial configuration support with limit overrides
   - Fix payment controller null safety issues
   - Improve pricing page UI with proper feature display
2025-11-21 10:55:57 -08:00

232 lines
6.5 KiB
PHP

<?php
namespace App\Livewire\Dashboard;
use App\Models\ActivationKey;
use App\Models\Plan;
use App\Models\PlanTier;
use App\Services\Payments\PaymentOrchestrator;
use Exception;
use Illuminate\Contracts\View\Factory;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Log;
use Livewire\Component;
class Pricing extends Component
{
public $plans;
public $planTiers;
public $activation_key;
public $selectedProvider = 'stripe';
public $selectedBillingCycle = null;
public $selectedTier = null;
public function mount(): void
{
$this->loadPlans();
$this->planTiers = PlanTier::with('plans')->orderBy('sort_order')->get();
}
private function loadPlans(): void
{
$query = Plan::active()
->ordered()
->with(['planFeatureLimits.planFeature', 'planProviders', 'trialConfiguration', 'planTier']);
if ($this->selectedTier) {
$query->where('plan_tier_id', $this->selectedTier);
}
$this->plans = $query->get();
}
public function filterByTier($tierId = null): void
{
$this->selectedTier = $tierId;
$this->loadPlans();
}
public function choosePlan($planId, $provider = 'stripe'): void
{
$plan = Plan::findOrFail($planId);
if (! $plan?->supportsProvider($provider)) {
session()->flash('error', "This plan doesn't support {$provider} payments.");
return;
}
$this->redirect(route('checkout.enhanced', [
'plan' => $planId,
'provider' => $provider,
]));
}
public function startTrial($planId, $provider = 'stripe'): void
{
$plan = Plan::findOrFail($planId);
if (! $plan?->hasTrial()) {
session()->flash('error', "This plan doesn't offer trials.");
return;
}
if (! $plan?->supportsProvider($provider)) {
session()->flash('error', "This plan doesn't support {$provider} payments for trials.");
return;
}
$this->redirect(route('checkout.trial', [
'plan' => $planId,
'provider' => $provider,
]));
}
public function activateKey(): void
{
$this->validate([
'activation_key' => 'required|alpha_num|max:30',
], [
'activation_key.required' => 'You must enter an activation key.',
'activation_key.alpha_num' => 'The activation key may only contain letters and numbers (no special characters).',
'activation_key.max' => 'The activation key must not exceed 30 characters.',
]);
$trimmedKey = trim((string) $this->activation_key);
$activation = ActivationKey::query()->where('activation_key', $trimmedKey)
->where('is_activated', false)
->first();
if ($activation) {
try {
$result = $this->activateSubscriptionKey($activation);
if ($result) {
session()->flash('success', 'Activation key is valid and has been activated. Refresh page to see changes.');
$this->reset('activation_key');
} else {
session()->flash('error', 'Something went wrong. Kindly drop a mail at contact@zemail.me to activate your subscription manually.');
}
} catch (Exception $e) {
Log::error('Activation key error: '.$e->getMessage());
session()->flash('error', 'An error occurred while activating your key. Please contact support.');
}
} else {
session()->flash('error', 'Invalid or already activated key.');
}
}
private function activateSubscriptionKey(ActivationKey $activation): bool
{
try {
// Use PaymentOrchestrator for activation key processing
$orchestrator = app(PaymentOrchestrator::class);
// Find the plan associated with this activation key
$plan = null;
if ($activation->plan_id) {
$plan = Plan::find($activation->plan_id);
} elseif ($activation->price_id) {
// Fallback to legacy pricing_id lookup
$plan = Plan::where('pricing_id', $activation->price_id)->first();
}
if (! $plan) {
Log::error('No plan found for activation key: '.$activation->id);
return false;
}
// Create subscription using orchestrator
$user = auth()->user();
$subscription = $orchestrator->createSubscriptionFromActivationKey($user, $activation, $plan);
if ($subscription) {
$activation->is_activated = true;
$activation->user_id = $user->id;
$activation->save();
return true;
}
return false;
} catch (Exception $e) {
Log::error('Activation key processing failed: '.$e->getMessage());
return false;
}
}
/**
* Get available providers for a plan
*/
public function getPlanProviders($planId): array
{
$plan = $this->plans->firstWhere('id', $planId);
return $plan ? $plan->getAllowedProviders() : [];
}
/**
* Get plan features with limits
*/
public function getPlanFeatures($planId): array
{
$plan = $this->plans->firstWhere('id', $planId);
if (! $plan) {
return [];
}
return $plan->getFeaturesWithLimits();
}
/**
* Check if plan has trial available
*/
public function planHasTrial($planId): bool
{
$plan = $this->plans->firstWhere('id', $planId);
return $plan ? $plan->hasTrial() : false;
}
/**
* Get trial configuration for plan
*/
public function getTrialConfig($planId): ?array
{
$plan = $this->plans->firstWhere('id', $planId);
if (! $plan || ! $plan->hasTrial()) {
return null;
}
$config = $plan->getTrialConfig();
return [
'duration_days' => $config->trial_duration_days,
'requires_payment_method' => $config->trial_requires_payment_method,
'auto_converts' => $config->trial_auto_converts,
];
}
/**
* Get billing cycle display text
*/
public function getBillingCycleDisplay($plan): string
{
return $plan->getBillingCycleDisplay();
}
public function render(): Factory|View
{
return view('livewire.dashboard.pricing');
}
}