Files
ladill-servers/app/Http/Controllers/Hosting/HostingController.php
T
isaaccladandCursor b6c8ac343f Extract Ladill Servers as standalone app at servers.ladill.com.
VPS and dedicated server ordering, managed panels, SSO, billing, and launcher integration — forked from hosting infrastructure with server-focused routes and dashboard.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 19:18:30 +00:00

279 lines
11 KiB
PHP

<?php
namespace App\Http\Controllers\Hosting;
use App\Http\Controllers\Controller;
use App\Models\HostingOrder;
use App\Services\Hosting\ResellerClubHostingService;
use App\Services\ResellerClub\ResellerClubCustomerService;
use App\Services\ResellerClub\ResellerClubProductService;
use App\Support\ResellerClubLegacy;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class HostingController extends Controller
{
public function index(Request $request, ResellerClubHostingService $hosting): RedirectResponse
{
return redirect()->route('hosting.single-domain');
}
public function show(
Request $request,
HostingOrder $hostingOrder,
ResellerClubHostingService $hosting,
ResellerClubProductService $products
): View
{
if ((int) $hostingOrder->user_id !== (int) $request->user()->id) {
abort(403);
}
$remoteDetails = null;
if ($hostingOrder->rc_order_id && $hosting->isConfigured()) {
$result = $hosting->getOrderDetails($hostingOrder->rc_order_id);
if ($result['success'] ?? false) {
$remoteDetails = $result['order'];
// Sync latest data
$hosting->syncOrderToLocal($result['order']['raw'] ?? $result['order'], $request->user()->id, $hostingOrder->domain_id);
$hostingOrder->refresh();
}
}
return view('hosting.show', [
'order' => $hostingOrder,
'remoteDetails' => $remoteDetails,
'renewalOptions' => $this->renewalOptionsForOrder($hostingOrder, $products),
]);
}
public function sync(
Request $request,
ResellerClubHostingService $hosting,
ResellerClubCustomerService $customers
): RedirectResponse {
$user = $request->user();
if (! $hosting->isConfigured()) {
return redirect()->route('hosting.index')->with('error', 'Hosting service is not configured.');
}
$resolved = $customers->resolveCustomerForUser($user);
if (! ($resolved['success'] ?? false)) {
return redirect()->route('hosting.index')
->with('error', $resolved['message'] ?? 'Could not prepare your ResellerClub customer.');
}
$synced = $hosting->syncCustomerOrders((string) $resolved['customer_id'], $user->id);
return redirect()->route('hosting.index')
->with('success', "Synced {$synced} hosting order(s) from ResellerClub.");
}
public function order(
Request $request,
ResellerClubHostingService $hosting,
ResellerClubCustomerService $customers
): JsonResponse {
$request->validate([
'domain_name' => 'required|string|max:253',
'plan_id' => 'required|string',
'months' => 'sometimes|integer|min:1|max:120',
]);
if (! $hosting->isConfigured()) {
return response()->json(['message' => 'Hosting service is not available at this time.'], 503);
}
$domainName = strtolower(trim($request->input('domain_name')));
$planId = $request->input('plan_id');
$months = $request->integer('months', 1);
$resolved = $customers->resolveCustomerForUser($request->user());
if (! ($resolved['success'] ?? false)) {
return response()->json([
'message' => $resolved['message'] ?? 'Could not prepare your ResellerClub customer.',
], 422);
}
$result = $hosting->orderHosting($domainName, $planId, (string) $resolved['customer_id'], $months);
if (! $result['success']) {
return response()->json(['message' => $result['message'] ?? 'Hosting order failed.'], 422);
}
// Fetch details and save locally
$order = null;
if (! empty($result['order_id'])) {
$details = $hosting->getOrderDetails($result['order_id']);
if ($details['success'] ?? false) {
$order = $hosting->syncOrderToLocal(
$details['order']['raw'] ?? $details['order'],
$request->user()->id
);
} else {
$order = HostingOrder::create([
'user_id' => $request->user()->id,
'domain_name' => $domainName,
'rc_order_id' => $result['order_id'],
'plan_name' => $planId,
'status' => HostingOrder::STATUS_PENDING,
]);
}
}
return response()->json([
'success' => true,
'message' => 'Hosting order placed successfully.',
'order' => $order?->only('id', 'domain_name', 'status', 'rc_order_id'),
]);
}
public function resetPanelPassword(Request $request, HostingOrder $hostingOrder, ResellerClubHostingService $hosting): JsonResponse
{
if ((int) $hostingOrder->user_id !== (int) $request->user()->id) {
abort(403);
}
$validated = $request->validate([
'password' => ['required', 'string', 'min:9', 'max:16', 'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[~*!@$#%_+.\?:,{}]).+$/'],
]);
if (! $hosting->isConfigured() || ! $hostingOrder->rc_order_id) {
return response()->json(['message' => 'Hosting service is not available.'], 503);
}
$result = $hosting->changePanelPassword($hostingOrder->rc_order_id, $validated['password']);
if (! $result['success']) {
return response()->json(['message' => $result['message'] ?? 'Could not update the cPanel password.'], 422);
}
return response()->json([
'success' => true,
'message' => 'cPanel password updated successfully.',
]);
}
public function renew(Request $request, HostingOrder $hostingOrder, ResellerClubHostingService $hosting): JsonResponse
{
if ((int) $hostingOrder->user_id !== (int) $request->user()->id) {
abort(403);
}
$request->validate([
'months' => 'sometimes|integer|min:1|max:120',
]);
if (! ResellerClubLegacy::renewalsEnabled()) {
return response()->json(['message' => ResellerClubLegacy::renewalsDisabledMessage()], 503);
}
if (! $hosting->isConfigured() || ! $hostingOrder->rc_order_id) {
return response()->json(['message' => 'Hosting service is not available.'], 503);
}
$result = $hosting->renewOrder($hostingOrder->rc_order_id, $request->integer('months', 1));
if (! $result['success']) {
return response()->json(['message' => $result['message'] ?? 'Renewal failed.'], 422);
}
// Re-sync details
$details = $hosting->getOrderDetails($hostingOrder->rc_order_id);
if ($details['success'] ?? false) {
$hosting->syncOrderToLocal($details['order']['raw'] ?? $details['order'], $request->user()->id, $hostingOrder->domain_id);
}
return response()->json(['success' => true, 'message' => 'Hosting renewed successfully.']);
}
public function destroy(Request $request, HostingOrder $hostingOrder, ResellerClubHostingService $hosting): RedirectResponse
{
if ((int) $hostingOrder->user_id !== (int) $request->user()->id) {
abort(403);
}
if ($hostingOrder->status === HostingOrder::STATUS_CANCELLED) {
$redirectRoute = $this->productRouteForOrder($hostingOrder);
$hostingOrder->delete();
return redirect()->route($redirectRoute)
->with('success', 'Cancelled hosting order deleted.');
}
if ($hostingOrder->rc_order_id && $hosting->isConfigured()) {
$result = $hosting->deleteOrder($hostingOrder->rc_order_id);
if (! $result['success']) {
return redirect()->route('hosting.show', $hostingOrder)
->with('error', $result['message'] ?? 'Could not cancel hosting at this time.');
}
}
$hostingOrder->update(['status' => HostingOrder::STATUS_CANCELLED]);
return redirect()->route($this->productRouteForOrder($hostingOrder))
->with('success', 'Hosting order cancelled.');
}
private function productRouteForOrder(HostingOrder $hostingOrder): string
{
return match ((string) $hostingOrder->hosting_type) {
HostingOrder::TYPE_MULTI_DOMAIN => 'hosting.multi-domain',
HostingOrder::TYPE_RESELLER => 'hosting.multi-domain',
default => 'hosting.single-domain',
};
}
/**
* @return array<int, array{months: string, label: string, total: ?string, monthly: ?string}>
*/
private function renewalOptionsForOrder(HostingOrder $hostingOrder, ResellerClubProductService $products): array
{
$category = match ((string) $hostingOrder->hosting_type) {
HostingOrder::TYPE_MULTI_DOMAIN => 'multi-domain-hosting',
HostingOrder::TYPE_RESELLER => 'reseller-hosting',
default => 'single-domain-hosting',
};
$packages = collect((array) ($products->formDefinition($category)['packages'] ?? []));
$planId = (string) ($hostingOrder->plan_id ?? '');
$planName = strtolower(trim((string) $hostingOrder->plan_name));
$package = $packages->first(function (array $item) use ($planId, $planName): bool {
$value = (string) ($item['value'] ?? '');
$label = strtolower(trim((string) ($item['label'] ?? '')));
return ($planId !== '' && $value === $planId)
|| ($planName !== '' && $label !== '' && $label === $planName);
});
if (! is_array($package)) {
return collect(['1', '3', '6', '12', '24', '36'])
->map(fn (string $months) => [
'months' => $months,
'label' => $months.' '.((int) $months === 1 ? 'Month' : 'Months'),
'total' => null,
'monthly' => null,
])
->all();
}
$priceField = ! empty((array) ($package['renew_prices'] ?? [])) ? 'renew_prices' : 'prices';
$totalField = ! empty((array) ($package['renew_totals'] ?? [])) ? 'renew_totals' : 'totals';
return collect(array_keys((array) ($package[$priceField] ?? [])))
->sortBy(fn (string $months) => (int) $months)
->values()
->map(fn (string $months) => [
'months' => $months,
'label' => $months.' '.((int) $months === 1 ? 'Month' : 'Months'),
'total' => (string) (($package[$totalField][$months] ?? null) ?: ''),
'monthly' => (string) (($package[$priceField][$months] ?? null) ?: ''),
])
->all();
}
}