where('slug', $request->validated('plan_slug'))->firstOrFail(); $domain = $request->domain(); if ($domain && Customer::query()->where('domain', $domain)->exists()) { return response()->json(['error' => 'Domain already taken.'], 422); } $result = DB::transaction(function () use ($request, $plan, $domain) { $user = User::query()->updateOrCreate( ['whmcs_client_id' => $request->integer('whmcs_client_id')], [ 'name' => $request->validated('client_name'), 'email' => $request->validated('client_email'), 'password' => Hash::make(Str::random(32)), 'role' => UserRole::Customer, 'is_active' => true, ], ); $customer = Customer::query()->create([ 'user_id' => $user->id, 'hosting_plan_id' => $plan->id, 'name' => $request->validated('hostname'), 'domain' => $domain ?? 'direct-'.Str::slug($request->validated('hostname')).'-'.Str::lower(Str::random(6)).'.internal', 'behind_traefik' => $request->boolean('behind_traefik', true), 'provision_mode' => $request->validated('provision_mode'), 'attached_iso' => $request->validated('iso_volid'), 'cpu' => $plan->cpu, 'ram' => $plan->ram, 'disk' => $plan->disk, 'status' => 'pending', 'provisioning_step' => 'queued', ]); $whmcsService = WhmcsService::query()->create([ 'whmcs_service_id' => $request->integer('whmcs_service_id'), 'whmcs_client_id' => $request->integer('whmcs_client_id'), 'whmcs_order_id' => $request->input('whmcs_order_id'), 'customer_id' => $customer->id, 'user_id' => $user->id, 'hosting_plan_id' => $plan->id, 'status' => 'provisioning', 'config' => $request->only([ 'provision_mode', 'template_slug', 'iso_volid', 'behind_traefik', 'subdomain', ]), ]); $customer->update(['whmcs_service_id' => $whmcsService->id]); $vmid = app(VmidReservationService::class)->reserveForCustomer($customer); $customer->update(['vmid' => $vmid]); ProvisionCustomerJob::dispatch($customer->id); return compact('customer', 'user', 'whmcsService', 'vmid'); }); return response()->json([ 'message' => 'Provisioning queued.', 'customer_id' => $result['customer']->id, 'vmid' => $result['vmid'], 'panel_url' => config('hosting.panel.url'), ], 202); } public function suspend(Request $request, int $whmcsServiceId): JsonResponse { $service = $this->resolveService($whmcsServiceId); $customer = $service->customer; if ($customer?->vmid) { app(\App\Services\Hosting\Proxmox\ProxmoxClient::class)->stopVM((int) $customer->vmid); } $service->update(['status' => 'suspended']); $customer?->update(['status' => 'failed', 'provisioning_step' => 'suspended']); return response()->json(['message' => 'Service suspended.']); } public function unsuspend(Request $request, int $whmcsServiceId): JsonResponse { $service = $this->resolveService($whmcsServiceId); $customer = $service->customer; if ($customer?->vmid) { app(\App\Services\Hosting\Proxmox\ProxmoxClient::class)->startVM((int) $customer->vmid); $customer->update(['status' => 'active', 'provisioning_step' => 'completed']); } $service->update(['status' => 'active']); return response()->json(['message' => 'Service unsuspended.']); } public function terminate(Request $request, int $whmcsServiceId, DeprovisionService $deprovision): JsonResponse { $service = $this->resolveService($whmcsServiceId); $customer = $service->customer; if ($customer) { $deprovision->terminatePermanently($customer); } $service->update(['status' => 'terminated', 'customer_id' => null]); return response()->json([ 'message' => 'Service terminated. VMID will be released after configured retention.', ]); } public function status(int $whmcsServiceId): JsonResponse { $service = $this->resolveService($whmcsServiceId); $service->load('customer'); return response()->json([ 'whmcs_service_id' => $service->whmcs_service_id, 'status' => $service->status, 'customer' => $service->customer ? [ 'id' => $service->customer->id, 'name' => $service->customer->name, 'vmid' => $service->customer->vmid, 'status' => $service->customer->status, 'provisioning_step' => $service->customer->provisioning_step, 'ip_address' => $service->customer->ip_address, 'domain' => $service->customer->domain, ] : null, ]); } private function resolveService(int $whmcsServiceId): WhmcsService { return WhmcsService::query() ->where('whmcs_service_id', $whmcsServiceId) ->firstOrFail(); } }