Deploy: 2026-07-15 05:08:07
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Request;
|
||||
use App\Core\Response;
|
||||
use App\Models\Company;
|
||||
|
||||
/**
|
||||
* Handles WhatsApp Cloud API (Meta Official) routes
|
||||
*/
|
||||
class CloudApiController extends BaseController
|
||||
{
|
||||
/**
|
||||
* Get the Cloud API connection status for the company
|
||||
*/
|
||||
public function status(Request $request, Response $response)
|
||||
{
|
||||
$companyId = $request->company_id;
|
||||
|
||||
$session = \App\Core\Database::selectOne(
|
||||
"SELECT * FROM meta_cloud_sessions WHERE company_id = ? LIMIT 1",
|
||||
[$companyId]
|
||||
);
|
||||
|
||||
if (!$session) {
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'data' => null
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide token from frontend
|
||||
unset($session['access_token']);
|
||||
unset($session['verify_token']);
|
||||
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'data' => $session
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect or update a Cloud API session
|
||||
*/
|
||||
public function connect(Request $request, Response $response)
|
||||
{
|
||||
$companyId = $request->company_id;
|
||||
$body = $request->getBody();
|
||||
|
||||
if (empty($body['phone_number_id']) || empty($body['waba_id']) || empty($body['access_token'])) {
|
||||
$response->status(400)->json(['error' => 'Missing required fields']);
|
||||
return;
|
||||
}
|
||||
|
||||
$session = \App\Core\Database::selectOne(
|
||||
"SELECT id FROM meta_cloud_sessions WHERE company_id = ? LIMIT 1",
|
||||
[$companyId]
|
||||
);
|
||||
|
||||
$verifyToken = 'nabeh_cloud_' . bin2hex(random_bytes(8));
|
||||
|
||||
if ($session) {
|
||||
\App\Core\Database::execute(
|
||||
"UPDATE meta_cloud_sessions SET phone_number_id = ?, waba_id = ?, access_token = ?, status = 'active' WHERE id = ?",
|
||||
[$body['phone_number_id'], $body['waba_id'], $body['access_token'], $session['id']]
|
||||
);
|
||||
} else {
|
||||
\App\Core\Database::execute(
|
||||
"INSERT INTO meta_cloud_sessions (company_id, phone_number_id, waba_id, access_token, verify_token, status) VALUES (?, ?, ?, ?, ?, 'active')",
|
||||
[$companyId, $body['phone_number_id'], $body['waba_id'], $body['access_token'], $verifyToken]
|
||||
);
|
||||
}
|
||||
|
||||
$updated = \App\Core\Database::selectOne(
|
||||
"SELECT * FROM meta_cloud_sessions WHERE company_id = ? LIMIT 1",
|
||||
[$companyId]
|
||||
);
|
||||
|
||||
unset($updated['access_token']);
|
||||
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'message' => 'WhatsApp Cloud API connected successfully',
|
||||
'data' => $updated
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect Cloud API
|
||||
*/
|
||||
public function disconnect(Request $request, Response $response)
|
||||
{
|
||||
$companyId = $request->company_id;
|
||||
|
||||
\App\Core\Database::execute(
|
||||
"DELETE FROM meta_cloud_sessions WHERE company_id = ?",
|
||||
[$companyId]
|
||||
);
|
||||
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Disconnected successfully'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch templates from Meta and sync them locally
|
||||
*/
|
||||
public function syncTemplates(Request $request, Response $response)
|
||||
{
|
||||
$companyId = $request->company_id;
|
||||
|
||||
$session = \App\Core\Database::selectOne(
|
||||
"SELECT * FROM meta_cloud_sessions WHERE company_id = ? LIMIT 1",
|
||||
[$companyId]
|
||||
);
|
||||
|
||||
if (!$session || $session['status'] !== 'active') {
|
||||
$response->status(400)->json(['error' => 'Cloud API not connected']);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Call the Node.js Cloud API Service via Gateway
|
||||
$gatewayUrl = rtrim(getenv('WHATSAPP_GATEWAY_URL') ?: 'http://localhost:3722', '/');
|
||||
$syncUrl = $gatewayUrl . '/api/cloud/templates/sync';
|
||||
|
||||
$payload = json_encode([
|
||||
'waba_id' => $session['waba_id'],
|
||||
'access_token' => $session['access_token']
|
||||
]);
|
||||
|
||||
$ch = curl_init($syncUrl);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'X-Webhook-Secret: ' . getenv('WEBHOOK_SECRET')
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
|
||||
$res = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($httpCode === 200) {
|
||||
$resData = json_decode($res, true);
|
||||
$templates = $resData['data'] ?? [];
|
||||
|
||||
// Clear old templates and insert new ones
|
||||
\App\Core\Database::execute("DELETE FROM message_templates WHERE company_id = ?", [$companyId]);
|
||||
|
||||
foreach ($templates as $tpl) {
|
||||
\App\Core\Database::execute(
|
||||
"INSERT INTO message_templates (company_id, meta_template_id, name, language, category, status, body_text) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
[$companyId, $tpl['id'], $tpl['name'], $tpl['language'], strtolower($tpl['category']), strtolower($tpl['status']), 'Synced from Meta']
|
||||
);
|
||||
}
|
||||
|
||||
$response->json([
|
||||
'status' => 'success',
|
||||
'message' => 'Templates synced successfully',
|
||||
'count' => count($templates)
|
||||
]);
|
||||
} else {
|
||||
$response->status($httpCode)->json(['error' => 'Failed to sync templates from Meta']);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$response->status(500)->json(['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Webhook to receive incoming messages from Meta
|
||||
*/
|
||||
public function webhook(Request $request, Response $response)
|
||||
{
|
||||
// Meta GET verification request
|
||||
if ($request->getMethod() === 'GET') {
|
||||
$mode = $request->get('hub_mode');
|
||||
$token = $request->get('hub_verify_token');
|
||||
$challenge = $request->get('hub_challenge');
|
||||
|
||||
if ($mode === 'subscribe' && $token) {
|
||||
$session = \App\Core\Database::selectOne(
|
||||
"SELECT * FROM meta_cloud_sessions WHERE verify_token = ? LIMIT 1",
|
||||
[$token]
|
||||
);
|
||||
|
||||
if ($session) {
|
||||
echo $challenge;
|
||||
exit;
|
||||
}
|
||||
}
|
||||
$response->status(403)->send('Forbidden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle POST incoming messages
|
||||
$body = $request->getBody();
|
||||
if (isset($body['entry'][0]['changes'][0]['value']['messages'])) {
|
||||
$metaMessages = $body['entry'][0]['changes'][0]['value']['messages'];
|
||||
$metadata = $body['entry'][0]['changes'][0]['value']['metadata'];
|
||||
$phoneNumberId = $metadata['phone_number_id'];
|
||||
|
||||
$session = \App\Core\Database::selectOne(
|
||||
"SELECT * FROM meta_cloud_sessions WHERE phone_number_id = ? AND status = 'active' LIMIT 1",
|
||||
[$phoneNumberId]
|
||||
);
|
||||
|
||||
if ($session) {
|
||||
foreach ($metaMessages as $msg) {
|
||||
// Log incoming message
|
||||
$msgText = $msg['text']['body'] ?? '[Media/Other]';
|
||||
\App\Models\MessageLog::logMessage([
|
||||
'company_id' => $session['company_id'],
|
||||
'contact_phone' => $msg['from'],
|
||||
'direction' => 'inbound',
|
||||
'message_type' => $msg['type'],
|
||||
'message_body' => $msgText,
|
||||
'whatsapp_message_id' => $msg['id'],
|
||||
'status' => 'read'
|
||||
]);
|
||||
|
||||
// Note: Here you can integrate Gemini AI for auto-replies over Cloud API
|
||||
// Similar to triggerAutoReply() in WhatsAppController
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->status(200)->send('EVENT_RECEIVED');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user