filled('hp_website')) { return response()->json([ 'success' => false, 'message' => 'Spam submission detected.', ], 422); } $desiredSubdomain = Str::slug($request->desired_subdomain); $request->merge(['desired_subdomain' => $desiredSubdomain]); $allowedModules = \App\Constants\ModuleContract::getAllInquiryKeys(); // Find existing inquiry by email to allow resubmissions by same user $existingInquiry = Inquiry::where('email', $request->email)->first(); // Prevent modifying subdomain slug if inquiry was already approved or activated if ($existingInquiry && in_array($existingInquiry->status, ['approved', 'active', 'provisioning'])) { if ($existingInquiry->desired_subdomain !== $desiredSubdomain) { return response()->json([ 'success' => false, 'message' => 'Your subdomain request has already been approved. Subdomain slug cannot be altered.', ], 422); } } $ignoreInquiryId = $existingInquiry ? $existingInquiry->id : null; $request->validate([ 'company_name' => 'required|string|max:255', 'contact_name' => 'required|string|max:255', 'email' => 'required|email|max:255', 'phone' => 'nullable|string|max:50', 'desired_subdomain' => [ 'required', 'string', 'max:50', 'alpha_dash', function ($attribute, $value, $fail) use ($ignoreInquiryId) { $slug = Str::slug($value); if (\App\Models\Tenant::where('id', $slug)->exists()) { $fail(__('This desired subdomain is already in use by an active tenant. Please choose a different subdomain.')); } if (\Stancl\Tenancy\Database\Models\Domain::where('domain', 'LIKE', $slug . '.%')->orWhere('domain', $slug)->exists()) { $fail(__('This desired subdomain is already in use by an active domain. Please choose a different subdomain.')); } $inquiryQuery = Inquiry::where('desired_subdomain', $slug); if ($ignoreInquiryId) { $inquiryQuery->where('id', '!=', $ignoreInquiryId); } if ($inquiryQuery->exists()) { $fail(__('This desired subdomain is already reserved or requested. Please choose a different subdomain slug.')); } }, ], 'employee_count' => 'nullable|integer|min:1|max:10000', 'requested_features' => 'nullable|array', 'requested_features.*' => 'string|in:' . implode(',', $allowedModules), 'notes' => 'nullable|string|max:2000', 'payment_reference' => 'nullable|string|max:255', ], [ 'requested_features.*.in' => 'One or more selected modules are invalid.', ]); $verificationCode = (string) rand(100000, 999999); // Store pending submission payload in Cache for 1 hour until email OTP is verified $cacheKey = 'pending_inquiry_' . md5(strtolower($request->email)); \Illuminate\Support\Facades\Cache::put($cacheKey, [ 'email' => $request->email, 'company_name' => $request->company_name, 'contact_name' => $request->contact_name, 'phone' => $request->phone, 'desired_subdomain' => $desiredSubdomain, 'employee_count' => $request->employee_count ?? 10, 'requested_features' => $request->requested_features ?? [], 'notes' => $request->notes ? Str::limit($request->notes, 2000, '...') : null, 'payment_reference' => $request->payment_reference ?? 'SUBDOMAIN-INQUIRY-WIZARD', 'verification_code' => $verificationCode, ], now()->addHour()); // Dispatch verification code email to inquiring user try { \Illuminate\Support\Facades\Mail::to($request->email)->send( new \App\Mail\InquiryVerificationCodeMail($request->company_name, $verificationCode) ); } catch (\Exception $e) { \Illuminate\Support\Facades\Log::warning("Could not send verification email to {$request->email}: " . $e->getMessage()); } return response()->json([ 'success' => true, 'requires_verification' => true, 'verification_code' => config('app.env') === 'testing' || config('app.debug') ? $verificationCode : null, 'message' => 'Subdomain inquiry details validated! Please enter the 6-digit verification code sent to your email to complete submission.', ], 200); } public function verifyEmail(Request $request) { $request->validate([ 'email' => 'required|email', 'verification_code' => 'required|string', ]); $cacheKey = 'pending_inquiry_' . md5(strtolower($request->email)); $pendingData = \Illuminate\Support\Facades\Cache::get($cacheKey); if (!$pendingData) { // Check if existing verified inquiry exists $existing = Inquiry::where('email', $request->email)->first(); if ($existing && $existing->email_verified_at) { return response()->json([ 'success' => true, 'message' => 'Your email has already been verified.', 'data' => $existing, ]); } return response()->json([ 'success' => false, 'message' => 'Verification session expired or no pending submission found. Please resubmit the inquiry form.', ], 422); } if ($pendingData['verification_code'] !== trim($request->verification_code)) { return response()->json([ 'success' => false, 'message' => 'Invalid verification code. Please check your email and try again.', ], 422); } // Save / Update Inquiry DB record ONLY after successful OTP verification! $existingInquiry = Inquiry::where('email', $pendingData['email'])->first(); if ($existingInquiry) { $inquiry = $existingInquiry; $combinedNotes = $pendingData['notes'] ? ($existingInquiry->notes ? $existingInquiry->notes . "\n---\n" . $pendingData['notes'] : $pendingData['notes']) : $existingInquiry->notes; $inquiry->update([ 'company_name' => $pendingData['company_name'], 'contact_name' => $pendingData['contact_name'], 'phone' => $pendingData['phone'], 'desired_subdomain' => $pendingData['desired_subdomain'], 'employee_count' => $pendingData['employee_count'], 'requested_features' => $pendingData['requested_features'], 'notes' => Str::limit($combinedNotes, 2000, ''), 'verification_code' => null, 'email_verified_at' => now(), 'payment_reference' => $pendingData['payment_reference'] ?? $existingInquiry->payment_reference, 'status' => 'pending_payment_approval', ]); } else { $inquiry = Inquiry::create([ 'email' => $pendingData['email'], 'company_name' => $pendingData['company_name'], 'contact_name' => $pendingData['contact_name'], 'phone' => $pendingData['phone'], 'desired_subdomain' => $pendingData['desired_subdomain'], 'employee_count' => $pendingData['employee_count'], 'requested_features' => $pendingData['requested_features'], 'notes' => $pendingData['notes'], 'verification_code' => null, 'email_verified_at' => now(), 'payment_reference' => $pendingData['payment_reference'], 'status' => 'pending_payment_approval', ]); } // Create matching Contact history entry ONLY after successful OTP verification! \App\Models\Contact::create([ 'email' => $pendingData['email'], 'name' => $pendingData['contact_name'], 'subject' => 'Subdomain Inquiry: ' . $pendingData['company_name'] . ' (' . $pendingData['desired_subdomain'] . ')', 'message' => $pendingData['notes'] ?? ('Subdomain request for ' . $pendingData['company_name'] . ' with ' . $pendingData['employee_count'] . ' employees.'), 'status' => 'New', ]); \Illuminate\Support\Facades\Cache::forget($cacheKey); return response()->json([ 'success' => true, 'message' => 'Email verified and inquiry submitted successfully! Your subdomain request is ready for Executive approval.', 'data' => $inquiry, ]); } }