Files
nabeh/backend/app/Controllers/OTPController.php
2026-05-23 01:45:24 +03:00

172 lines
6.8 KiB
PHP

<?php
namespace App\Controllers;
use App\Core\Request;
use App\Core\Response;
use App\Core\Flows\ConversationFlowEngine;
use App\Services\TTSService;
use App\Models\WhatsAppSession;
use App\Models\CompanySubscription;
use App\Models\CompanySubscriptionUsage;
class OTPController extends BaseController
{
/**
* Send OTP verification code via WhatsApp (Text or Voice Note)
* POST /api/otp/send
*/
public function send(Request $request, Response $response): void
{
$companyId = $request->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 including +)
$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
$useElevenLabs = false;
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') {
$features = json_decode($activeSub['features'] ?: '{}', true);
$voiceFeatureEnabled = isset($features['voice']) ? (bool)$features['voice'] : false;
$hasVoiceLimit = CompanySubscriptionUsage::hasRemainingLimit($companyId, 'voice');
if ($voiceFeatureEnabled && $hasVoiceLimit) {
$useElevenLabs = true;
}
}
} else {
if ($type === 'voice') {
$useElevenLabs = true;
}
}
// 3. Generate verification code
$code = $customCode ? trim($customCode) : (string)rand(1000, 9999);
// 4. Send Message
try {
$usedElevenLabs = false;
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 = null;
$mimeType = 'audio/mp3';
if ($useElevenLabs) {
$rule = \App\Models\ChatbotRule::findActiveForRule($companyId, $session['id']);
$configuredElKey = ($rule && !empty($rule['elevenlabs_api_key'])) ? $rule['elevenlabs_api_key'] : null;
$elApiKey = \App\Services\GeminiService::getElevenLabsApiKey($configuredElKey);
$configuredVoiceId = ($rule && !empty($rule['elevenlabs_voice_id'])) ? $rule['elevenlabs_voice_id'] : null;
$elVoiceId = \App\Services\GeminiService::getElevenLabsVoiceId($configuredVoiceId);
if (!empty($elApiKey)) {
$elVoiceId = !empty($elVoiceId) ? $elVoiceId : 'pNInz6obpgDQGcFmaJgB'; // Default to Adam
$audioData = \App\Services\GeminiService::generateAudioResponseWithElevenLabs($elApiKey, $textToRead, $elVoiceId);
if ($audioData) {
$audioBase64 = $audioData['audio'];
$mimeType = $audioData['mimeType'];
$usedElevenLabs = true;
}
}
}
if (!$audioBase64) {
// Fallback to Google Translate TTS
$audioBase64 = TTSService::textToSpeechArabic($textToRead);
$mimeType = 'audio/mp3';
$usedElevenLabs = false;
}
if (!$audioBase64) {
$response->status(500)->json(['error' => 'Failed to generate voice OTP audio.']);
return;
}
// Send voice note
ConversationFlowEngine::sendReply($session, $phone, '', null, $audioBase64, $mimeType);
} else {
// Send text
$textMsg = "رمز التحقق الخاص بك لمتجر نابه هو: *{$code}* \n الرجاء عدم مشاركته مع أي شخص.";
ConversationFlowEngine::sendReply($session, $phone, $textMsg);
}
// Increment usage stats
if ($companyId !== 1) {
CompanySubscriptionUsage::incrementUsage($companyId, 'request');
if ($type === 'voice' && $usedElevenLabs) {
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()]);
}
}
}