- NormalBot: nueva funcion api_report para descargar PDF/Excel del ERP y enviarlo por WhatsApp - NormalBot: resolveMenu() para referenciar menus por nombre en los flujos - WhatsAppSender: uploadMedia() y sendDocument() para envio de documentos - BotRouter: guarda respuestas en conversations para visibilidad en el chat - Script apply-menus.php con estructura completa de menus y endpoints
166 lines
5.6 KiB
PHP
166 lines
5.6 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
class WhatsAppSender
|
|
{
|
|
private const API_VERSION = 'v18.0';
|
|
private const BASE_URL = 'https://graph.facebook.com';
|
|
|
|
public static function sendText(string $to, string $text, string $phoneNumberId): array
|
|
{
|
|
return self::callApi($phoneNumberId, [
|
|
'messaging_product' => 'whatsapp',
|
|
'recipient_type' => 'individual',
|
|
'to' => $to,
|
|
'type' => 'text',
|
|
'text' => ['body' => $text],
|
|
]);
|
|
}
|
|
|
|
public static function sendTemplate(string $to, string $templateName, string $phoneNumberId, array $components = []): array
|
|
{
|
|
$payload = [
|
|
'messaging_product' => 'whatsapp',
|
|
'recipient_type' => 'individual',
|
|
'to' => $to,
|
|
'type' => 'template',
|
|
'template' => [
|
|
'name' => $templateName,
|
|
'language' => ['code' => 'es'],
|
|
],
|
|
];
|
|
|
|
if (!empty($components)) {
|
|
$payload['template']['components'] = $components;
|
|
}
|
|
|
|
return self::callApi($phoneNumberId, $payload);
|
|
}
|
|
|
|
public static function sendImage(string $to, string $mediaIdOrUrl, string $phoneNumberId, ?string $caption = null): array
|
|
{
|
|
$payload = [
|
|
'messaging_product' => 'whatsapp',
|
|
'recipient_type' => 'individual',
|
|
'to' => $to,
|
|
'type' => 'image',
|
|
'image' => [
|
|
(str_starts_with($mediaIdOrUrl, 'http') ? 'link' : 'id') => $mediaIdOrUrl,
|
|
],
|
|
];
|
|
|
|
if ($caption !== null) {
|
|
$payload['image']['caption'] = $caption;
|
|
}
|
|
|
|
return self::callApi($phoneNumberId, $payload);
|
|
}
|
|
|
|
public static function sendInteractive(string $to, array $interactive, string $phoneNumberId): array
|
|
{
|
|
return self::callApi($phoneNumberId, [
|
|
'messaging_product' => 'whatsapp',
|
|
'recipient_type' => 'individual',
|
|
'to' => $to,
|
|
'type' => 'interactive',
|
|
'interactive' => $interactive,
|
|
]);
|
|
}
|
|
|
|
public static function uploadMedia(string $filePath, string $mimeType, string $phoneNumberId): array
|
|
{
|
|
$url = self::BASE_URL . '/' . self::API_VERSION . '/' . $phoneNumberId . '/media';
|
|
$token = env('WHATSAPP_ACCESS_TOKEN', '');
|
|
|
|
if ($token === '') {
|
|
return ['success' => false, 'error' => 'WHATSAPP_ACCESS_TOKEN no configurado'];
|
|
}
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => [
|
|
'messaging_product' => 'whatsapp',
|
|
'file' => new \CURLFile($filePath, $mimeType, basename($filePath)),
|
|
],
|
|
CURLOPT_HTTPHEADER => [
|
|
'Authorization: Bearer ' . $token,
|
|
],
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 60,
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$error = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
$decoded = $response ? json_decode($response, true) : null;
|
|
unlink($filePath);
|
|
|
|
return [
|
|
'success' => $httpCode >= 200 && $httpCode < 300,
|
|
'http_code' => $httpCode,
|
|
'media_id' => $decoded['id'] ?? null,
|
|
'response' => $decoded ?? $response,
|
|
'error' => $error ?: null,
|
|
];
|
|
}
|
|
|
|
public static function sendDocument(string $to, string $mediaId, string $phoneNumberId, ?string $caption = null, ?string $filename = null): array
|
|
{
|
|
$document = ['id' => $mediaId];
|
|
if ($filename !== null) {
|
|
$document['filename'] = $filename;
|
|
}
|
|
if ($caption !== null) {
|
|
$document['caption'] = $caption;
|
|
}
|
|
|
|
return self::callApi($phoneNumberId, [
|
|
'messaging_product' => 'whatsapp',
|
|
'recipient_type' => 'individual',
|
|
'to' => $to,
|
|
'type' => 'document',
|
|
'document' => $document,
|
|
]);
|
|
}
|
|
|
|
private static function callApi(string $phoneNumberId, array $payload): array
|
|
{
|
|
$url = self::BASE_URL . '/' . self::API_VERSION . '/' . $phoneNumberId . '/messages';
|
|
$token = env('WHATSAPP_ACCESS_TOKEN', '');
|
|
|
|
if ($token === '') {
|
|
return ['success' => false, 'error' => 'WHATSAPP_ACCESS_TOKEN no configurado'];
|
|
}
|
|
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_POSTFIELDS => json_encode($payload),
|
|
CURLOPT_HTTPHEADER => [
|
|
'Authorization: Bearer ' . $token,
|
|
'Content-Type: application/json',
|
|
],
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 15,
|
|
]);
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$error = curl_error($ch);
|
|
curl_close($ch);
|
|
|
|
$decoded = $response ? json_decode($response, true) : null;
|
|
|
|
return [
|
|
'success' => $httpCode >= 200 && $httpCode < 300,
|
|
'http_code' => $httpCode,
|
|
'response' => $decoded ?? $response,
|
|
'error' => $error ?: ($decoded['error']['message'] ?? null),
|
|
'wam_id' => $decoded['messages'][0]['id'] ?? null,
|
|
];
|
|
}
|
|
}
|