198 lines
6.8 KiB
PHP
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)
|
|
];
|
|
}
|
|
}
|