Files
whatsapp/services/WhatsAppService.php
T
2025-11-18 20:26:36 -05:00

336 lines
9.6 KiB
PHP

<?php
/**
* Servicio de WhatsApp - Envío de mensajes
* Fecha: 13 de noviembre de 2025
*/
class WhatsAppService {
private $token;
private $phoneNumberId;
private $apiUrl;
private $db;
public function __construct() {
$this->token = WHATSAPP_TOKEN;
$this->phoneNumberId = WHATSAPP_PHONE_NUMBER_ID;
$this->apiUrl = WHATSAPP_API_URL;
$this->db = Database::getInstance();
}
/**
* Enviar mensaje de texto
*/
public function sendTextMessage($to, $message) {
$data = [
'messaging_product' => 'whatsapp',
'to' => $this->formatPhoneNumber($to),
'type' => 'text',
'text' => [
'body' => $message
]
];
return $this->sendMessage($data);
}
/**
* Enviar mensaje con template
*/
public function sendTemplateMessage($to, $templateName, $language = 'es', $parameters = []) {
$template = [
'name' => $templateName,
'language' => [
'code' => $language
]
];
if (!empty($parameters)) {
$template['components'] = [
[
'type' => 'body',
'parameters' => $parameters
]
];
}
$data = [
'messaging_product' => 'whatsapp',
'to' => $this->formatPhoneNumber($to),
'type' => 'template',
'template' => $template
];
return $this->sendMessage($data);
}
/**
* Enviar mensaje con botones interactivos
*/
public function sendInteractiveMessage($to, $bodyText, $buttons, $header = null, $footer = null) {
$interactive = [
'type' => 'button',
'body' => [
'text' => $bodyText
],
'action' => [
'buttons' => $buttons
]
];
if ($header) {
$interactive['header'] = [
'type' => 'text',
'text' => $header
];
}
if ($footer) {
$interactive['footer'] = [
'text' => $footer
];
}
$data = [
'messaging_product' => 'whatsapp',
'recipient_type' => 'individual',
'to' => $this->formatPhoneNumber($to),
'type' => 'interactive',
'interactive' => $interactive
];
return $this->sendMessage($data);
}
/**
* Enviar mensaje con lista
*/
public function sendListMessage($to, $bodyText, $buttonText, $sections, $header = null, $footer = null) {
$interactive = [
'type' => 'list',
'body' => [
'text' => $bodyText
],
'action' => [
'button' => $buttonText,
'sections' => $sections
]
];
if ($header) {
$interactive['header'] = [
'type' => 'text',
'text' => $header
];
}
if ($footer) {
$interactive['footer'] = [
'text' => $footer
];
}
$data = [
'messaging_product' => 'whatsapp',
'recipient_type' => 'individual',
'to' => $this->formatPhoneNumber($to),
'type' => 'interactive',
'interactive' => $interactive
];
return $this->sendMessage($data);
}
/**
* Marcar mensaje como leído
*/
public function markAsRead($messageId) {
$data = [
'messaging_product' => 'whatsapp',
'status' => 'read',
'message_id' => $messageId
];
$url = $this->apiUrl . $this->phoneNumberId . '/messages';
return $this->makeRequest('POST', $url, $data);
}
/**
* Enviar mensaje principal
*/
private function sendMessage($data) {
$url = $this->apiUrl . $this->phoneNumberId . '/messages';
$response = $this->makeRequest('POST', $url, $data);
// Guardar mensaje enviado en la base de datos
if ($response && isset($response['messages'][0]['id'])) {
$this->saveOutgoingMessage($data, $response);
}
return $response;
}
/**
* Realizar petición HTTP
*/
private function makeRequest($method, $url, $data = null) {
$ch = curl_init();
$headers = [
'Authorization: Bearer ' . $this->token,
'Content-Type: application/json',
'User-Agent: WhatsApp-Bot/1.0'
];
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3
]);
if ($method === 'POST' && $data) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
error_log("cURL Error: " . $error);
throw new Exception("Error de comunicación con WhatsApp: " . $error);
}
$decoded = json_decode($response, true);
if ($httpCode >= 400) {
$errorMsg = isset($decoded['error']['message']) ? $decoded['error']['message'] : 'Error desconocido';
error_log("WhatsApp API Error: " . $response);
throw new Exception("Error de WhatsApp API: " . $errorMsg);
}
return $decoded;
}
/**
* Formatear número de teléfono
*/
private function formatPhoneNumber($phone) {
// Remover caracteres especiales
$phone = preg_replace('/[^0-9]/', '', $phone);
// Si empieza con +57 (Colombia), mantenerlo
if (substr($phone, 0, 2) === '57' && strlen($phone) >= 12) {
return $phone;
}
// Si es número colombiano sin código de país
if (strlen($phone) === 10 && substr($phone, 0, 1) === '3') {
return '57' . $phone;
}
return $phone;
}
/**
* Guardar mensaje enviado en BD
*/
private function saveOutgoingMessage($data, $response) {
try {
$user = $this->getUserByPhone($data['to']);
if ($user) {
$messageData = [
'user_id' => $user['id'],
'message_id' => $response['messages'][0]['id'],
'direction' => 'outgoing',
'message_type' => $data['type'],
'content' => $this->extractMessageContent($data),
'status' => 'sent',
'created_at' => date('Y-m-d H:i:s')
];
$this->db->insert('conversations', $messageData);
}
} catch (Exception $e) {
error_log("Error saving outgoing message: " . $e->getMessage());
}
}
/**
* Extraer contenido del mensaje para guardar
*/
private function extractMessageContent($data) {
switch ($data['type']) {
case 'text':
return $data['text']['body'];
case 'template':
return 'Template: ' . $data['template']['name'];
case 'interactive':
if (isset($data['interactive']['body']['text'])) {
return $data['interactive']['body']['text'];
}
break;
}
return json_encode($data);
}
/**
* Obtener usuario por teléfono
*/
private function getUserByPhone($phone) {
return $this->db->fetch(
"SELECT * FROM users WHERE phone_number = :phone",
['phone' => $phone]
);
}
/**
* Descargar archivo multimedia
*/
public function downloadMedia($mediaId) {
$url = $this->apiUrl . $mediaId;
// Primero obtener información del archivo
$mediaInfo = $this->makeRequest('GET', $url);
if (!$mediaInfo || !isset($mediaInfo['url'])) {
throw new Exception("No se pudo obtener información del archivo");
}
// Descargar el archivo
$fileUrl = $mediaInfo['url'];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $fileUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->token
],
CURLOPT_TIMEOUT => 60,
CURLOPT_FOLLOWLOCATION => true
]);
$fileContent = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("Error descargando archivo multimedia");
}
return [
'content' => $fileContent,
'mime_type' => $mediaInfo['mime_type'] ?? 'application/octet-stream',
'file_size' => $mediaInfo['file_size'] ?? strlen($fileContent)
];
}
}
?>