Wire Ladill domain picker and purchase modal into hosting flows.
Deploy Ladill Hosting / deploy (push) Successful in 51s

Use the Domains API for owned domains across panel, account, and order forms, with an embedded iframe purchase modal instead of redirecting to domains.ladill.com.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-19 18:01:23 +00:00
co-authored by Cursor
parent 44cfb1d9b9
commit 537e34cbd7
20 changed files with 532 additions and 306 deletions
@@ -1116,6 +1116,18 @@ class HostingPanelController extends Controller
$onboardingMode = $isOwnedDomain
? Domain::MODE_NS_AUTO
: ($validated['onboarding_mode'] ?? Domain::MODE_NS_AUTO);
if ($isOwnedDomain) {
$owned = $this->availableHostingDomainsForUser($request->user()->id, $account);
if (! $owned->contains($domainHost)) {
if ($request->wantsJson()) {
return response()->json(['message' => 'That domain is not in your Ladill account.'], 422);
}
return back()->with('error', 'That domain is not in your Ladill account.');
}
}
$serverIp = $account->node?->ip_address ?? '161.97.138.149';
$docRoot = ! empty($validated['document_root'])
? "/home/{$account->username}/" . ltrim($validated['document_root'], '/')
@@ -1297,35 +1309,13 @@ class HostingPanelController extends Controller
->filter()
->all();
$domainRegistryHosts = Domain::where('user_id', $userId)->pluck('host');
$user = \App\Models\User::query()->find($userId);
$owned = $user
? app(\App\Services\Domains\LadillDomainsClient::class)->ownedForUser((string) $user->public_id)
: [];
$registeredDomains = RcServiceOrder::where('user_id', $userId)
->whereIn('order_type', [RcServiceOrder::TYPE_DOMAIN_REGISTRATION, RcServiceOrder::TYPE_DOMAIN_TRANSFER])
->where('status', RcServiceOrder::STATUS_ACTIVE)
->whereNotNull('domain_name')
->pluck('domain_name');
$customerHostingDomains = CustomerHostingOrder::where('user_id', $userId)
->whereNotIn('status', [
CustomerHostingOrder::STATUS_PENDING_PAYMENT,
CustomerHostingOrder::STATUS_CANCELLED,
CustomerHostingOrder::STATUS_FAILED,
])
->whereNotNull('domain_name')
->pluck('domain_name');
$hostingAccountDomains = HostingAccount::where('user_id', $userId)
->whereNotNull('primary_domain')
->pluck('primary_domain');
return $domainRegistryHosts
->merge($registeredDomains)
->merge($customerHostingDomains)
->merge($hostingAccountDomains)
->map(fn ($d) => strtolower(trim((string) $d)))
->filter(fn ($d) => (bool) preg_match('/^(?=.{1,253}$)(?!-)(?:[a-z0-9-]{1,63}\.)+[a-z]{2,63}$/', $d))
return collect($owned)
->reject(fn ($d) => in_array($d, $linkedDomains, true))
->unique()
->sort()
->values();
}
@@ -206,7 +206,7 @@ class HostingProductController extends Controller
'linkedSites' => $account->sites->sortBy(fn ($site) => [$site->type !== 'primary', $site->domain])->values(),
'maxDomains' => $maxDomains,
'canLinkMoreDomains' => $account->sites->count() < $maxDomains,
'ownedDomains' => $this->getUserDomains($request->user()),
'ownedDomains' => collect($this->getUserDomains($request->user())),
'emailUsage' => [
'mailbox_count' => $mailboxCount,
'free_allowance' => $freeEmailAllowance,
@@ -630,17 +630,9 @@ class HostingProductController extends Controller
return $product->catalogSummary();
}
private function getUserDomains($user): \Illuminate\Support\Collection
private function getUserDomains($user): array
{
return \App\Models\Domain::query()
->where('user_id', $user->id)
->whereNull('hosting_account_id')
->whereDoesntHave('hostedSites')
->where(function ($query) {
$query->where('source', 'purchased')
->orWhereNotNull('email_domain_id');
})
->orderBy('host')
->get(['id', 'host', 'status', 'onboarding_state']);
return app(\App\Services\Domains\LadillDomainsClient::class)
->ownedForUser((string) $user->public_id);
}
}
@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers;
use App\Services\Domains\LadillDomainsClient;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class OwnedDomainsController extends Controller
{
public function __invoke(Request $request, LadillDomainsClient $domains): JsonResponse
{
$exclude = collect($request->query('exclude', []))
->map(fn ($d) => strtolower(trim((string) $d)))
->filter()
->all();
$owned = collect($domains->ownedForUser((string) ladill_account()->public_id))
->reject(fn ($d) => in_array($d, $exclude, true))
->sort()
->values()
->all();
return response()->json(['data' => $owned]);
}
}
@@ -0,0 +1,42 @@
<?php
namespace App\Services\Domains;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class LadillDomainsClient
{
/** @return list<string> */
public function ownedForUser(string $publicId): array
{
$key = (string) (config('domains.api_key') ?? '');
if ($key === '') {
return [];
}
try {
$res = Http::withToken($key)
->acceptJson()
->timeout(10)
->get(rtrim((string) config('domains.api_url'), '/').'/owned-domains', [
'user' => $publicId,
]);
$res->throw();
return collect($res->json('data', []))
->map(fn ($d) => strtolower(trim((string) $d)))
->filter()
->values()
->all();
} catch (\Throwable $e) {
Log::warning('LadillDomainsClient: could not load owned domains', [
'user' => $publicId,
'error' => $e->getMessage(),
]);
return [];
}
}
}
+11
View File
@@ -23,6 +23,17 @@ if (! function_exists('ladill_domains_url')) {
}
}
if (! function_exists('ladill_domains_embed_url')) {
function ladill_domains_embed_url(string $path = '/embed/find'): string
{
$url = ladill_domains_url($path);
$origin = rtrim((string) config('app.url'), '/');
$separator = str_contains($url, '?') ? '&' : '?';
return $url.$separator.'parent_origin='.urlencode($origin);
}
}
if (! function_exists('ladill_home_url')) {
function ladill_home_url(string $path = ''): string
{