post("{$this->arbiterBase}/activate", [ 'order_id' => $orderId, 'domain' => $domain, 'ip' => request()->server('SERVER_ADDR', ''), ]); $data = $response->json(); if ($response->successful() && ($data['status'] ?? '') === 'active') { $this->blueprint->dbSet('modpackchecker', 'order_id', $orderId); $this->blueprint->dbSet('modpackchecker', 'license_status', 'active'); $this->blueprint->dbSet('modpackchecker', 'tier', $data['tier'] ?? 'standard'); $this->blueprint->dbSet('modpackchecker', 'last_validated', now()->toISOString()); $this->blueprint->dbSet('modpackchecker', 'grace_expires', ''); return ['success' => true, 'tier' => $data['tier'] ?? 'standard']; } return ['success' => false, 'error' => $data['error'] ?? 'Activation failed']; } catch (\Exception $e) { Log::error('[MVC License] Activation error: ' . $e->getMessage()); return ['success' => false, 'error' => 'Could not reach license server. Try again later.']; } } /** * Validate (phone-home). Called daily by mvc:validate Artisan command. * On failure, enters grace period. After 7 days, marks expired. */ public function validate(): array { $orderId = $this->blueprint->dbGet('modpackchecker', 'order_id'); if (empty($orderId)) { return ['status' => 'inactive', 'message' => 'No license configured']; } $domain = config('app.url'); try { $response = Http::timeout(15)->post("{$this->arbiterBase}/validate", [ 'order_id' => $orderId, 'domain' => $domain, 'version' => config('app.version', '1.0.0'), 'php_version' => PHP_VERSION, ]); $data = $response->json(); if ($response->successful() && ($data['status'] ?? '') === 'active') { $this->blueprint->dbSet('modpackchecker', 'license_status', 'active'); $this->blueprint->dbSet('modpackchecker', 'tier', $data['tier'] ?? 'standard'); $this->blueprint->dbSet('modpackchecker', 'last_validated', now()->toISOString()); $this->blueprint->dbSet('modpackchecker', 'grace_expires', ''); if (!empty($data['latest_version'])) { $this->blueprint->dbSet('modpackchecker', 'latest_version', $data['latest_version']); } return ['status' => 'active', 'tier' => $data['tier'] ?? 'standard']; } return $this->enterGracePeriod($data['error'] ?? 'Validation failed'); } catch (\Exception $e) { Log::warning('[MVC License] Validation failed: ' . $e->getMessage()); return $this->enterGracePeriod('Could not reach license server'); } } /** * Enter or continue grace period. After 7 days, expire. */ private function enterGracePeriod(string $reason): array { $graceExpires = $this->blueprint->dbGet('modpackchecker', 'grace_expires'); if (empty($graceExpires)) { // First failure — start grace period $expires = now()->addDays($this->graceDays)->toISOString(); $this->blueprint->dbSet('modpackchecker', 'license_status', 'grace'); $this->blueprint->dbSet('modpackchecker', 'grace_expires', $expires); Log::warning("[MVC License] Grace period started. Expires: {$expires}. Reason: {$reason}"); return ['status' => 'grace', 'expires' => $expires, 'reason' => $reason]; } // Check if grace period expired if (now()->greaterThan($graceExpires)) { $this->blueprint->dbSet('modpackchecker', 'license_status', 'expired'); Log::error('[MVC License] Grace period expired. License marked as expired.'); return ['status' => 'expired', 'reason' => $reason]; } return ['status' => 'grace', 'expires' => $graceExpires, 'reason' => $reason]; } /** * Deactivate license on this panel. */ public function deactivate(): array { $orderId = $this->blueprint->dbGet('modpackchecker', 'order_id'); if (empty($orderId)) { return ['success' => true]; } try { Http::timeout(15)->post("{$this->arbiterBase}/deactivate", [ 'order_id' => $orderId, 'domain' => config('app.url'), ]); } catch (\Exception $e) { // Best-effort — clear local state regardless } $this->blueprint->dbSet('modpackchecker', 'order_id', ''); $this->blueprint->dbSet('modpackchecker', 'license_status', 'inactive'); $this->blueprint->dbSet('modpackchecker', 'tier', 'standard'); $this->blueprint->dbSet('modpackchecker', 'grace_expires', ''); return ['success' => true]; } /** * Check if a pro feature is allowed based on current tier + license status. */ public function isProFeatureAllowed(): bool { $status = $this->blueprint->dbGet('modpackchecker', 'license_status'); $tier = $this->blueprint->dbGet('modpackchecker', 'tier'); if ($status === 'expired') { return false; } return $tier === 'professional'; } /** * Get full license state for the admin UI. */ public function getState(): array { return [ 'order_id' => $this->blueprint->dbGet('modpackchecker', 'order_id') ?: '', 'status' => $this->blueprint->dbGet('modpackchecker', 'license_status') ?: 'inactive', 'tier' => $this->blueprint->dbGet('modpackchecker', 'tier') ?: 'standard', 'grace_expires' => $this->blueprint->dbGet('modpackchecker', 'grace_expires') ?: '', 'last_validated' => $this->blueprint->dbGet('modpackchecker', 'last_validated') ?: '', 'latest_version' => $this->blueprint->dbGet('modpackchecker', 'latest_version') ?: '', ]; } }