webmoney/backend/app/Models/Plan.php
marcoitaloesp-ai 0adb5c889f
feat(subscriptions): sistema de assinaturas SaaS v1.49.0
- Criar tabela plans com Free, Pro Monthly, Pro Annual
- Criar tabela subscriptions com status e integração PayPal
- Criar tabela invoices com numeração sequencial WM-YYYY-NNNNNN
- Models: Plan, Subscription, Invoice com helpers
- User: hasActiveSubscription(), onTrial(), currentPlan(), etc.
- API: GET /api/plans (público)
- Seeder: PlansSeeder com 3 planos base
- Fase 2 do roadmap SaaS concluída
2025-12-17 10:46:34 +00:00

161 lines
3.7 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Plan extends Model
{
use HasFactory;
protected $fillable = [
'name',
'slug',
'description',
'price',
'currency',
'billing_period',
'trial_days',
'features',
'limits',
'is_active',
'is_featured',
'sort_order',
'paypal_plan_id',
];
protected $casts = [
'price' => 'decimal:2',
'features' => 'array',
'limits' => 'array',
'is_active' => 'boolean',
'is_featured' => 'boolean',
'trial_days' => 'integer',
'sort_order' => 'integer',
];
// ==================== RELATIONSHIPS ====================
public function subscriptions(): HasMany
{
return $this->hasMany(Subscription::class);
}
// ==================== SCOPES ====================
public function scopeActive($query)
{
return $query->where('is_active', true);
}
public function scopeOrdered($query)
{
return $query->orderBy('sort_order')->orderBy('price');
}
public function scopeFree($query)
{
return $query->where('billing_period', 'free');
}
public function scopePaid($query)
{
return $query->where('billing_period', '!=', 'free');
}
// ==================== ACCESSORS ====================
public function getIsFreeAttribute(): bool
{
return $this->billing_period === 'free' || $this->price == 0;
}
public function getHasTrialAttribute(): bool
{
return $this->trial_days > 0;
}
public function getFormattedPriceAttribute(): string
{
if ($this->is_free) {
return 'Gratis';
}
$symbols = [
'EUR' => '€',
'USD' => '$',
'BRL' => 'R$',
];
$symbol = $symbols[$this->currency] ?? $this->currency;
$period = match ($this->billing_period) {
'monthly' => '/mes',
'annual' => '/año',
'lifetime' => ' (único)',
default => '',
};
return $symbol . number_format($this->price, 2, ',', '.') . $period;
}
public function getMonthlyPriceAttribute(): float
{
if ($this->billing_period === 'annual') {
return round($this->price / 12, 2);
}
return $this->price;
}
public function getSavingsPercentAttribute(): ?int
{
if ($this->billing_period !== 'annual') {
return null;
}
// Find monthly equivalent
$monthlyPlan = static::where('billing_period', 'monthly')
->where('is_active', true)
->where('price', '>', 0)
->first();
if (!$monthlyPlan) {
return null;
}
$annualEquivalent = $monthlyPlan->price * 12;
$savings = (($annualEquivalent - $this->price) / $annualEquivalent) * 100;
return (int) round($savings);
}
// ==================== METHODS ====================
public function hasFeature(string $feature): bool
{
return in_array($feature, $this->features ?? []);
}
public function getLimit(string $key, $default = null)
{
return $this->limits[$key] ?? $default;
}
/**
* Get the default free plan
*/
public static function getFreePlan(): ?self
{
return static::where('slug', 'free')->first();
}
/**
* Get plans for pricing page
*/
public static function getForPricing(): \Illuminate\Database\Eloquent\Collection
{
return static::active()->ordered()->get();
}
}