billing->canCreate($user)) { throw new RuntimeException('Insufficient wallet balance. Top up to create links.'); } return DB::transaction(function () use ($user, $data) { $slug = $this->resolveSlug($data['custom_slug'] ?? null); $wallet = $user->getOrCreateLinkWallet(); $link = ShortLink::create([ 'user_id' => $user->id, 'source_app' => 'link', 'source_kind' => 'redirect', 'is_managed_here' => true, 'slug' => $slug, 'label' => $data['label'] ?? null, 'destination_url' => $this->normalizeUrl($data['destination_url']), 'expires_at' => $data['expires_at'] ?? null, ]); $this->billing->debitForLinkCreation($wallet, $link); return $link; }); } public function update(ShortLink $link, array $data): ShortLink { $link->fill([ 'label' => $data['label'] ?? $link->label, 'destination_url' => isset($data['destination_url']) ? $this->normalizeUrl($data['destination_url']) : $link->destination_url, 'is_active' => $data['is_active'] ?? $link->is_active, 'expires_at' => array_key_exists('expires_at', $data) ? $data['expires_at'] : $link->expires_at, ])->save(); return $link->fresh(); } public function resolve(Request $request, string $slug): ?ShortLink { $link = ShortLink::query() ->where('slug', $slug) ->where('is_active', true) ->first(); if ($link === null || $link->isExpired()) { return null; } $this->clickRecorder->record($link, $request); return $link; } private function resolveSlug(?string $custom): string { if ($custom !== null && $custom !== '') { $custom = strtolower(trim($custom)); if (! preg_match('/^[a-z0-9][a-z0-9-]{1,18}[a-z0-9]$/', $custom)) { throw new RuntimeException('Custom slug must be 3–20 characters: lowercase letters, numbers, and hyphens.'); } if (ShortLink::where('slug', $custom)->exists()) { throw new RuntimeException('That slug is already taken.'); } return $custom; } do { $slug = Str::lower(Str::random((int) config('link.slug_length', 8))); } while (ShortLink::where('slug', $slug)->exists()); return $slug; } private function normalizeUrl(string $url): string { $url = trim($url); if ($url === '') { throw new RuntimeException('Destination URL is required.'); } if (! preg_match('#^https?://#i', $url)) { $url = 'https://'.$url; } if (! filter_var($url, FILTER_VALIDATE_URL)) { throw new RuntimeException('Enter a valid URL.'); } return $url; } }