settings ?? []; $plan = (string) ($settings['plan'] ?? 'free'); if (in_array($plan, ['pro', 'enterprise'], true) && ! empty($settings['plan_expires_at'])) { if (Carbon::parse($settings['plan_expires_at'])->isPast()) { return 'free'; } } return array_key_exists($plan, config('frontdesk.plans', [])) ? $plan : 'free'; } public function isPro(Organization $organization): bool { return $this->planKey($organization) === 'pro'; } public function isEnterprise(Organization $organization): bool { return $this->planKey($organization) === 'enterprise'; } public function hasPaidPlan(Organization $organization): bool { return in_array($this->planKey($organization), ['pro', 'enterprise'], true); } /** @return array */ public function plan(Organization $organization): array { $key = $this->planKey($organization); return [ 'key' => $key, ...(array) config('frontdesk.plans.'.$key, []), ]; } /** Null means unlimited included host emails (Pro / Enterprise). */ public function freeEmailsPerMonth(Organization $organization): ?int { $value = config('frontdesk.plans.'.$this->planKey($organization).'.free_emails_per_month', 100); return $value === null ? null : (int) $value; } public function activeBranchCount(Organization $organization): int { return Branch::query() ->where('organization_id', $organization->id) ->where('is_active', true) ->count(); } public function canAddBranch(Organization $organization, int $currentCount): bool { $limit = config('frontdesk.plans.'.$this->planKey($organization).'.max_branches'); return $limit === null || $currentCount < (int) $limit; } public function canAddKiosk(Organization $organization): bool { $limit = config('frontdesk.plans.'.$this->planKey($organization).'.max_kiosk_devices'); if ($limit === null) { return true; } $count = Device::query() ->where('organization_id', $organization->id) ->where('type', 'kiosk') ->count(); return $count < (int) $limit; } public function hasFeature(Organization $organization, string $feature): bool { $features = config('frontdesk.plans.'.$this->planKey($organization).'.features', []); return in_array($feature, $features, true); } public function proPricePerBranchMinor(): int { return (int) config( 'frontdesk.plans.pro.price_minor_per_branch', config('frontdesk.plans.pro.price_minor', 99000), ); } /** * Per-branch rate (legacy callers treated this as a flat monthly fee). * * @deprecated Prefer proPricePerBranchMinor() / priceForBranches(). */ public function proPriceMinor(): int { return $this->proPricePerBranchMinor(); } public function enterprisePricePerBranchMinor(): int { return (int) config('frontdesk.plans.enterprise.price_minor_per_branch', 199000); } public function proPriceTotalMinor(Organization $organization): int { return $this->priceForBranches('pro', $this->activeBranchCount($organization)); } public function enterprisePriceMinor(Organization $organization): int { return $this->priceForBranches('enterprise', $this->activeBranchCount($organization)); } public function priceForBranches(string $plan, int $branches): int { $branches = max(1, $branches); $rate = $plan === 'enterprise' ? $this->enterprisePricePerBranchMinor() : $this->proPricePerBranchMinor(); return $branches * $rate; } public function resolveCheckoutBranches(Organization $organization, int $requested): int { $branches = max(1, $requested); $active = max(1, $this->activeBranchCount($organization)); return max($branches, $active); } public function canSubscribeEnterprise(Organization $organization): bool { return $this->activeBranchCount($organization) >= (int) config('frontdesk.enterprise.min_branches', 1); } }