<?php
 namespace App\Models; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Facades\Crypt; class ClientSoftwareLicense extends Model { public const TYPE_LIFETIME = 'lifetime'; public const TYPE_SUBSCRIPTION = 'subscription'; protected $fillable = [ 'client_product_id', 'client_id', 'product_label', 'module', 'issued', 'issued_at', 'expires_at', 'revoked_at', 'license_type', 'license_key', 'host_binding', 'binding_input', 'module_data', 'last_error', 'last_attempt_at', ]; protected $casts = [ 'host_binding' => 'array', 'binding_input' => 'array', 'module_data' => 'array', 'issued' => 'boolean', 'issued_at' => 'datetime', 'expires_at' => 'datetime', 'revoked_at' => 'datetime', 'last_attempt_at' => 'datetime', ]; protected $hidden = ['license_key']; protected static function booted(): void { static::saving(function (self $license): void { $plain = $license->license_key; $license->license_key_hash = $plain ? hash('sha256', (string) $plain) : null; }); } public function clientProduct(): BelongsTo { return $this->belongsTo(ClientProduct::class); } public function client(): BelongsTo { return $this->belongsTo(Client::class); } public function isActive(): bool { if (! $this->issued || $this->revoked_at !== null) { return false; } if ($this->license_type === self::TYPE_LIFETIME) { return true; } return $this->expires_at === null || $this->expires_at->isFuture(); } public function isLifetime(): bool { return $this->license_type === self::TYPE_LIFETIME; } public function isRevoked(): bool { return $this->revoked_at !== null; } public function isExpired(): bool { return ! $this->isLifetime() && $this->expires_at !== null && $this->expires_at->isPast(); } public function daysUntilExpiry(): ?int { if (! $this->expires_at) { return null; } return (int) round($this->expires_at->diffInDays(now(), false) * -1); } public function scopeIssued($q) { return $q->where('issued', true); } public function scopePendingIssue($q) { return $q->where('issued', false)->whereNull('revoked_at'); } protected function licenseKey(): Attribute { return Attribute::make( get: fn ($value) => $value === null ? null : $this->safeDecrypt($value), set: fn ($value) => $value === null ? null : Crypt::encryptString($value), ); } private function safeDecrypt(string $value): string { try { return Crypt::decryptString($value); } catch (\Throwable $e) { return $value; } } } 