'boolean', 'first_year' => 'integer', 'sort_order' => 'integer', ]; public function scopeDomains($query) { return $query->where('type', 'domain'); } public function scopeServices($query) { return $query->where('type', 'service'); } /** Cast to int when the value is a plain number (e.g. "300000"), otherwise keep as-is (e.g. "Miễn phí"). */ private static function numericOrString(?string $value): string|int { return is_numeric($value) ? (int) $value : (string) $value; } public function orderPrice(int $years): int { $setupFee = is_numeric($this->setup_fee) ? (int) $this->setup_fee : 0; return $setupFee + (int) $this->first_year + (int) $this->recurring_fee * (max(1, $years) - 1); } public function toDomainArray(): array { return [ 'tld' => $this->tld, 'setupFee' => self::numericOrString($this->setup_fee), 'firstYear' => (int) $this->first_year, 'renewal' => (int) $this->recurring_fee, ]; } public function toServiceArray(): array { return [ 'name' => $this->name, 'setupFee' => self::numericOrString($this->setup_fee), 'maintenance' => self::numericOrString($this->recurring_fee), 'note' => (string) $this->note, ]; } /** Shape consumed by both the client and admin domain-pricing endpoints. */ public static function toResponseArray(): array { $rows = static::query()->where('is_active', true)->orderBy('sort_order')->get(); return [ 'domains' => $rows->where('type', 'domain')->map->toDomainArray()->values()->all(), 'services' => $rows->where('type', 'service')->map->toServiceArray()->values()->all(), ]; } /** * Longest-TLD-match lookup used when pricing a domain order. * * @throws \InvalidArgumentException if no matching TLD is configured */ public static function findByDomain(string $domain): self { return static::matchDomain($domain) ?? throw new \InvalidArgumentException('Tên miền không được hỗ trợ. Vui lòng chọn đuôi tên miền có trong danh sách.'); } public static function matchDomain(string $domain): ?self { $domain = mb_strtolower(trim($domain)); $candidates = static::domains()->where('is_active', true)->get() ->sortByDesc(fn (self $row) => strlen((string) $row->tld)); foreach ($candidates as $row) { $tld = mb_strtolower(ltrim((string) $row->tld, '.')); if ($tld !== '' && str_ends_with($domain, '.' . $tld)) { return $row; } } return null; } public static function yearsForMonths(int $months): int { return max(1, (int) ceil($months / 12)); } }