trangshopeeaffiliate/app/Services/ZaloBotService.php
2026-07-11 22:56:22 +07:00

238 lines
7.9 KiB
PHP

<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class ZaloBotService
{
protected LinkConverterService $linkConverterService;
protected SettingService $settingService;
public function __construct(
LinkConverterService $linkConverterService,
SettingService $settingService
) {
$this->linkConverterService = $linkConverterService;
$this->settingService = $settingService;
}
/**
* Handle incoming Zalo Bot webhook request.
*/
public function handleWebhook(array $payload): void
{
Log::info('Zalo Webhook payload received', $payload);
// Check if message structure exists (mimics Telegram format)
$message = $payload['message'] ?? null;
if (!$message) {
return;
}
$chatId = $message['chat']['id'] ?? null;
$text = $message['text'] ?? '';
if (!$chatId || empty($text)) {
return;
}
// Get sender name (fallback to "Bạn")
$fromName = trim(
($message['from']['first_name'] ?? '') . ' ' . ($message['from']['last_name'] ?? '')
);
if (empty($fromName)) {
$fromName = $message['from']['username'] ?? 'Bạn';
}
// Find Shopee links
$urls = $this->extractShopeeUrls($text);
if (empty($urls)) {
return;
}
foreach ($urls as $url) {
$this->processAndReplyLink($chatId, $fromName, $url);
}
}
/**
* Process a single Shopee link and reply to the chat.
*/
protected function processAndReplyLink(string $chatId, string $fromName, string $url): void
{
try {
// 1. Resolve redirects if it's a short URL (e.g. s.shopee.vn or shp.ee)
$resolvedUrl = $this->resolveShortUrl($url);
// 2. Extract product name from resolved URL
$productName = $this->extractProductName($resolvedUrl) ?? 'Sản phẩm Shopee';
// 3. Estimate commission
$commission = $this->estimateCommission($resolvedUrl);
// 4. Convert link to our local affiliate short link
$convertResult = $this->linkConverterService->process($resolvedUrl);
if ($convertResult && $convertResult->isSuccess) {
$shortLink = $convertResult->affiliateUrl;
// 5. Construct the message template matching competitor's chatbot
$replyText = "Hi @{$fromName}, Link của bạn đây ạ:\n\n" .
"📦 {$productName}\n" .
"🔗 Link: {$shortLink}\n" .
"💵 Hoa hồng ước tính: {$commission}\n\n" .
"⚠️ Lưu ý: Để hoa hồng hợp lệ, KHÔNG mua qua Livestream, KHÔNG xem video/live bất kỳ sau khi click & XÓA sản phẩm ra khỏi giỏ hàng TRƯỚC KHI bấm link!";
// 6. Send reply message
$this->sendMessage($chatId, $replyText);
} else {
Log::error("Failed to convert Shopee URL via Zalo: " . ($convertResult->errorMessage ?? 'Unknown error'));
}
} catch (\Exception $e) {
Log::error("Error processing link in ZaloBotService: " . $e->getMessage());
}
}
/**
* Extract all Shopee URLs from text.
*/
public function extractShopeeUrls(string $text): array
{
$pattern = '/(?:https?:\/\/)?(?:[a-zA-Z0-9-]+\.)?shopee\.vn\/[^\s]+|(?:https?:\/\/)?shp\.ee\/[^\s]+/i';
if (preg_match_all($pattern, $text, $matches)) {
$urls = [];
foreach ($matches[0] as $url) {
if (!str_starts_with(strtolower($url), 'http://') && !str_starts_with(strtolower($url), 'https://')) {
$url = 'https://' . $url;
}
$urls[] = $url;
}
return $urls;
}
return [];
}
/**
* Resolve short URLs (s.shopee.vn or shp.ee) to get the final redirected URL.
*/
public function resolveShortUrl(string $url): string
{
if (str_contains($url, '/an_redir') || (!str_contains($url, 's.shopee.vn') && !str_contains($url, 'shp.ee'))) {
return $url;
}
try {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true); // Get headers only
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // Check redirect location manually
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36');
$response = curl_exec($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($info['http_code'] >= 300 && $info['http_code'] < 400) {
if (preg_match('/[Ll]ocation:\s*(.+)/', $response, $matches)) {
$resolvedUrl = trim($matches[1]);
// Recursively resolve redirect if it's still a redirect link
return $this->resolveShortUrl($resolvedUrl);
}
}
} catch (\Exception $e) {
Log::warning("Could not resolve short URL redirect: " . $e->getMessage());
}
return $url;
}
/**
* Extract product/shop name from Shopee URL.
*/
public function extractProductName(string $url): ?string
{
$parsedUrl = parse_url($url);
if (!isset($parsedUrl['path'])) {
return null;
}
$path = urldecode($parsedUrl['path']);
$path = trim($path, '/');
// Match typical Shopee product paths ending with -i.SHOP_ID.ITEM_ID
if (preg_match('/^(.*?)-i\.\d+\.\d+$/', $path, $matches)) {
$slug = $matches[1];
$name = str_replace(['-', '_'], ' ', $slug);
return trim($name);
}
// Shop pages / Usernames
if (!str_contains($path, '/')) {
$name = str_replace(['-', '_'], ' ', $path);
return trim($name);
}
return null;
}
/**
* Predictable estimate of product commission based on the item ID.
*/
public function estimateCommission(string $url): string
{
$itemId = 0;
if (preg_match('/-i\.\d+\.(\d+)$/', $url, $matches)) {
$itemId = (int)$matches[1];
}
if ($itemId === 0) {
$itemId = crc32($url);
}
// Predictable single estimate between 4.0% and 7.5% based on itemId or crc32
$rate = 4.0 + (abs($itemId) % 36) / 10.0;
return number_format($rate, 1) . '%';
}
/**
* Send message using Zalo Bot Creator HTTP API.
*/
public function sendMessage(string $chatId, string $text): bool
{
$token = $this->settingService->get('zalo_bot_token') ?? env('ZALO_BOT_TOKEN');
if (!$token) {
Log::error('Zalo Bot Token is not configured. Cannot send message.');
return false;
}
$apiUrl = "https://bot-api.zaloplatforms.com/bot{$token}/sendMessage";
Log::info('Sending message to Zalo Bot', [
'chat_id' => $chatId,
'url' => "https://bot-api.zaloplatforms.com/bot[HIDDEN]/sendMessage"
]);
$response = Http::timeout(10)->post($apiUrl, [
'chat_id' => $chatId,
'text' => $text,
]);
if ($response->successful()) {
Log::info('Message sent successfully to Zalo Bot');
return true;
}
Log::error('Failed to send message to Zalo Bot', [
'status' => $response->status(),
'body' => $response->body()
]);
return false;
}
}