company_id; $body = $request->getBody(); $phone = $body['phone'] ?? ''; $type = $body['type'] ?? 'text'; // 'text' or 'voice' $sessionId = $body['session_id'] ?? null; $customCode = $body['code'] ?? null; if (empty($phone)) { $response->status(400)->json(['error' => 'Missing required parameter: phone']); return; } // Clean phone number (remove non-digits except +) $phone = preg_replace('/[^\d+]/', '', $phone); // 1. Resolve WhatsApp Session $session = null; if ($sessionId) { $session = WhatsAppSession::findSecure((int)$sessionId); if (!$session || (int)$session['company_id'] !== (int)$companyId) { $response->status(404)->json(['error' => 'WhatsApp session not found']); return; } } else { // Grab the first connected session of the company $sessions = WhatsAppSession::findAllByCompany($companyId); foreach ($sessions as $s) { if ($s['status'] === 'connected') { $session = $s; break; } } if (!$session && !empty($sessions)) { $session = $sessions[0]; // fallback to first session if none is connected } } if (!$session) { $response->status(400)->json(['error' => 'No active WhatsApp sessions configured for this company.']); return; } if ($session['status'] !== 'connected') { $response->status(400)->json(['error' => 'WhatsApp session is not connected. Connect the session first.']); return; } // 2. Check SaaS subscription quotas if ($companyId !== 1) { $activeSub = CompanySubscription::findActiveByCompany($companyId); if (!$activeSub) { $response->status(402)->json(['error' => 'Active subscription plan required.']); return; } if (!CompanySubscriptionUsage::hasRemainingLimit($companyId, 'request')) { $response->status(403)->json(['error' => 'Monthly request quota exceeded. Please upgrade your plan.']); return; } if ($type === 'voice') { if (!CompanySubscriptionUsage::hasRemainingLimit($companyId, 'voice')) { $response->status(403)->json(['error' => 'Voice request quota exceeded. Please upgrade your plan.']); return; } // Starter plan doesn't support Voice Notes $features = json_decode($activeSub['features'] ?: '{}', true); if (isset($features['voice']) && !$features['voice']) { $response->status(403)->json(['error' => 'Voice OTP is not supported in your current subscription plan.']); return; } } } // 3. Generate verification code $code = $customCode ? trim($customCode) : (string)rand(1000, 9999); // 4. Send Message try { if ($type === 'voice') { // Spacing the digits to force slow Arabic pronunciation: e.g. "1 2 3 4" $spacedCode = implode(' ', str_split($code)); $textToRead = "رمز التحقق الخاص بك هو: {$spacedCode}. أكرر، رمز التحقق هو: {$spacedCode}."; $audioBase64 = TTSService::textToSpeechArabic($textToRead); if (!$audioBase64) { $response->status(500)->json(['error' => 'Failed to generate voice OTP audio.']); return; } // Send voice note ConversationFlowEngine::sendReply($session, $phone, '', null, $audioBase64, 'audio/mp3'); } else { // Send text $textMsg = "رمز التحقق الخاص بك لمتجر نابه هو: *{$code}* \n الرجاء عدم مشاركته مع أي شخص."; ConversationFlowEngine::sendReply($session, $phone, $textMsg); } // Increment usage stats if ($companyId !== 1) { CompanySubscriptionUsage::incrementUsage($companyId, 'request'); if ($type === 'voice') { CompanySubscriptionUsage::incrementUsage($companyId, 'voice'); } } $response->json([ 'status' => 'success', 'message' => 'OTP sent successfully', 'code' => $code, 'type' => $type ]); } catch (\Exception $e) { error_log("[OTP Controller Error] " . $e->getMessage()); $response->status(500)->json(['error' => 'Failed to send OTP message: ' . $e->getMessage()]); } } }