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

198 lines
6.8 KiB
PHP

<?php
namespace App\Services;
use App\DTOs\AffiliateResult;
use App\Interfaces\AffiliateConverterInterface;
class LinkConverterService
{
protected AffiliateConverterInterface $converter;
protected HistoryService $historyService;
protected SettingService $settingService;
public function __construct(
AffiliateConverterInterface $converter,
HistoryService $historyService,
SettingService $settingService
) {
$this->converter = $converter;
$this->historyService = $historyService;
$this->settingService = $settingService;
}
public function process(
string $url,
?string $zaloThreadId = null,
?string $zaloChatName = null,
string $sourceType = 'web',
?string $customerName = null,
?string $customerPhone = null
): AffiliateResult
{
// 1. Generate unique code beforehand for tracking
$code = null;
do {
$code = \Illuminate\Support\Str::random(10);
} while (\App\Models\ConversionHistory::whereRaw('BINARY code = ?', [$code])->exists());
// 2. Fetch real Shopee product information
$productInfo = $this->fetchShopeeProductInfo($url);
$productTitle = $productInfo['name'] ?? null;
$productImage = $productInfo['image'] ?? null;
$productPrice = $productInfo['price'] ?? 0.00;
// Determine estimated commission rate and amount
$commissionData = $this->calculateEstimatedCommission($productTitle, $productPrice, $productInfo['is_mall'] ?? false);
$estimatedCommission = $commissionData['amount'];
$maxRetries = $this->settingService->get('retry_count', 3);
$attempts = 0;
$result = null;
while ($attempts < $maxRetries) {
$attempts++;
// 3. Convert with the pre-generated code for sub_id mapping
$result = $this->converter->convert($url, $code);
if ($result->isSuccess) {
break;
}
}
// Save history regardless of success or failure
if ($result) {
$history = $this->historyService->logConversion(
$result,
$code,
$zaloThreadId,
$zaloChatName,
$customerName,
$customerPhone,
$productTitle,
$productImage,
$productPrice,
$estimatedCommission
);
// Override converter type if triggered from external source (like Zalo)
if ($history && $sourceType !== 'web') {
$history->update([
'converter_type' => $sourceType
]);
}
if ($result->isSuccess && $history->code) {
$domain = $this->settingService->get('short_link_domain');
if ($domain) {
$domain = rtrim($domain, '/');
$result->affiliateUrl = $domain . '/aff/s/' . $history->code;
} else {
$result->affiliateUrl = route('short_link.redirect', ['code' => $history->code]);
}
}
}
return $result;
}
/**
* Fetch product details (title, image, price) from Shopee internal API.
*/
public function fetchShopeeProductInfo(string $url): ?array
{
if (!preg_match('/i\.(\d+)\.(\d+)/', $url, $matches)) {
return null;
}
$shopId = $matches[1];
$itemId = $matches[2];
$apiUrl = "https://shopee.vn/api/v4/item/get?itemid={$itemId}&shopid={$shopId}";
try {
$response = \Illuminate\Support\Facades\Http::timeout(5)
->withHeaders([
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept' => 'application/json',
'Referer' => 'https://shopee.vn/',
])
->get($apiUrl);
if ($response->successful()) {
$data = $response->json();
$item = $data['data'] ?? null;
if ($item) {
$price = isset($item['price']) ? $item['price'] / 100000 : 0;
$image = isset($item['image']) ? "https://down-vn.img.susercontent.com/file/" . $item['image'] : null;
return [
'name' => $item['name'] ?? null,
'price' => $price,
'image' => $image,
'is_mall' => !empty($item['is_official_shop']),
'description' => $item['description'] ?? null
];
}
}
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::warning("Shopee internal API lookup failed: " . $e->getMessage());
}
return null;
}
/**
* Calculate estimated commission based on category keywords and Mall status.
*/
protected function calculateEstimatedCommission(?string $title, float $price, bool $isMall): array
{
if (!$title || $price <= 0) {
return ['rate' => 0, 'amount' => 0.00];
}
$titleLower = mb_strtolower($title);
// Define category keywords
$fashionKeywords = ['áo', 'quần', 'váy', 'đầm', 'giày', 'túi', 'kính', 'son', 'kem', 'phấn', 'nước hoa', 'tắm', 'gội', 'phụ kiện', 'trang sức', 'skincare', 'scrub'];
$electronicsKeywords = ['điện thoại', 'laptop', 'tivi', 'loa', 'tai nghe', 'máy ảnh', 'sạc', 'cáp', 'chuột', 'bàn phím', 'camera', 'micro', 'usb', 'thẻ nhớ', 'router', 'wifi', 'pin'];
$isFashion = false;
foreach ($fashionKeywords as $keyword) {
if (str_contains($titleLower, $keyword)) {
$isFashion = true;
break;
}
}
$isElectronics = false;
if (!$isFashion) {
foreach ($electronicsKeywords as $keyword) {
if (str_contains($titleLower, $keyword)) {
$isElectronics = true;
break;
}
}
}
// Apply rates
if ($isFashion) {
$rate = $isMall ? 7.4 : 3.5;
} elseif ($isElectronics) {
$rate = $isMall ? 1.5 : 0.8;
} else {
// General categories
$rate = $isMall ? 5.0 : 2.5;
}
// Allow admin settings override if configured
$adminOverrideRate = $this->settingService->get('commission_rate_override');
if ($adminOverrideRate !== null && $adminOverrideRate !== '') {
$rate = (float)$adminOverrideRate;
}
return [
'rate' => $rate,
'amount' => round($price * ($rate / 100), 2)
];
}
}