prepare("SELECT * FROM contacts WHERE id = :id LIMIT 1"); $stmt->execute(['id' => $contactId]); $contact = $stmt->fetch(); if (!$contact) { return ['success' => false, 'message' => 'Contato não encontrado']; } if (empty($contact['phone'])) { return ['success' => false, 'message' => 'Contato sem telefone']; } $phone = preg_replace('/[^0-9]/', '', $contact['phone']); $settings = self::getSettings(); // ========================= // ENVIO DE TEMPLATE // ========================= if ($templateId) { $tpl = $db->prepare("SELECT * FROM whatsapp_templates WHERE id = :id LIMIT 1"); $tpl->execute(['id' => $templateId]); $template = $tpl->fetch(); if (!$template) { return ['success' => false, 'message' => 'Template não encontrado']; } $payload = [ 'messaging_product' => 'whatsapp', 'to' => $phone, 'type' => 'template', 'template' => [ 'name' => $template['template_key'], 'language' => [ 'code' => $template['language'] ?? 'pt_BR' ] ] ]; $response = self::sendPayload($settings, $payload); if (!$response['success']) { self::log("Erro template: " . json_encode($response)); return $response; } self::saveMessage($contactId, '[TEMPLATE] ' . $template['name'], $response['message_id']); return ['success' => true]; } // ========================= // BLOQUEIO 24H // ========================= if (!self::isInside24hWindow($contactId)) { return [ 'success' => false, 'code' => 'outside_24h', 'message' => 'Fora da janela de 24h' ]; } // ========================= // ENVIO NORMAL // ========================= $payload = [ 'messaging_product' => 'whatsapp', 'to' => $phone, 'type' => 'text', 'text' => [ 'body' => $message ] ]; $response = self::sendPayload($settings, $payload); if (!$response['success']) { self::log("Erro texto: " . json_encode($response)); return $response; } self::saveMessage($contactId, $message, $response['message_id']); return ['success' => true]; } catch (Exception $e) { self::log($e->getMessage()); return ['success' => false, 'message' => $e->getMessage()]; } } private static function sendPayload($settings, $payload) { $url = "https://graph.facebook.com/v18.0/" . $settings['phone_number_id'] . "/messages"; $ch = curl_init($url); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "Authorization: Bearer " . $settings['access_token'], "Content-Type: application/json" ]); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $result = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http >= 200 && $http < 300) { $json = json_decode($result, true); return [ 'success' => true, 'message_id' => $json['messages'][0]['id'] ?? null ]; } return [ 'success' => false, 'error' => $result ]; } private static function isInside24hWindow($contactId) { $db = db(); $stmt = $db->prepare(" SELECT created_at FROM whatsapp_messages WHERE contact_id = :id AND direction = 'inbound' ORDER BY created_at DESC LIMIT 1 "); $stmt->execute(['id' => $contactId]); $last = $stmt->fetchColumn(); if (!$last) return false; return (time() - strtotime($last)) <= (60 * 60 * 24); } private static function saveMessage($contactId, $body, $messageId) { $db = db(); $stmt = $db->prepare(" INSERT INTO whatsapp_messages (contact_id, direction, provider_status, external_message_id, message_body, created_at) VALUES (:contact_id, 'outbound', 'sent', :msg_id, :body, NOW()) "); $stmt->execute([ 'contact_id' => $contactId, 'msg_id' => $messageId, 'body' => $body ]); } private static function getSettings() { return [ 'access_token' => getenv('META_ACCESS_TOKEN'), 'phone_number_id' => getenv('META_PHONE_NUMBER_ID') ]; } private static function log($msg) { file_put_contents(__DIR__ . '/../../storage/logs/whatsapp.log', date('Y-m-d H:i:s') . ' ' . $msg . PHP_EOL, FILE_APPEND); } }