Files
links/application/common/service/SplitPixelPostbackService.php
T

170 lines
5.8 KiB
PHP
Raw Normal View History

2026-06-05 04:22:29 +08:00
<?php
declare(strict_types=1);
namespace app\common\service;
use think\Log;
/**
* 分流中转页服务端像素回传(Facebook CAPI / TikTok Events API
*/
class SplitPixelPostbackService
{
/**
* @param array{facebook: array<int, array<string, mixed>>, tiktok: array<int, array<string, mixed>>} $config
*/
public static function dispatch(array $config, string $clientIp, string $userAgent, string $eventSourceUrl = ''): void
{
foreach (SplitPixelConfigService::getEnabledSorted($config, SplitPixelConfigService::PLATFORM_FACEBOOK) as $row) {
if ((int) ($row['server_postback'] ?? 0) !== 1) {
continue;
}
$token = trim((string) ($row['access_token'] ?? ''));
if ($token === '') {
continue;
}
self::sendFacebook($row, $token, $clientIp, $userAgent, $eventSourceUrl);
}
foreach (SplitPixelConfigService::getEnabledSorted($config, SplitPixelConfigService::PLATFORM_TIKTOK) as $row) {
if ((int) ($row['server_postback'] ?? 0) !== 1) {
continue;
}
$token = trim((string) ($row['access_token'] ?? ''));
if ($token === '') {
continue;
}
self::sendTikTok($row, $token, $clientIp, $userAgent, $eventSourceUrl);
}
}
/**
* @param array<string, mixed> $row
*/
private static function sendFacebook(array $row, string $accessToken, string $clientIp, string $userAgent, string $eventSourceUrl): void
{
$pixelId = trim((string) ($row['pixel_id'] ?? ''));
if ($pixelId === '') {
return;
}
$eventName = (string) ($row['event'] ?? 'PageView');
$testCode = trim((string) ($row['test_code'] ?? ''));
$payload = [
'data' => [[
'event_name' => $eventName,
'event_time' => time(),
'action_source' => 'website',
'user_data' => array_filter([
'client_ip_address' => $clientIp !== '' ? $clientIp : null,
'client_user_agent' => $userAgent !== '' ? $userAgent : null,
]),
]],
];
if ($eventSourceUrl !== '') {
$payload['data'][0]['event_source_url'] = $eventSourceUrl;
}
if ($testCode !== '') {
$payload['test_event_code'] = $testCode;
}
$url = 'https://graph.facebook.com/v19.0/' . rawurlencode($pixelId) . '/events?access_token=' . rawurlencode($accessToken);
self::postJson($url, $payload, [], 'facebook', $pixelId);
}
/**
* @param array<string, mixed> $row
*/
private static function sendTikTok(array $row, string $accessToken, string $clientIp, string $userAgent, string $eventSourceUrl): void
{
$pixelId = trim((string) ($row['pixel_id'] ?? ''));
if ($pixelId === '') {
return;
}
$event = self::mapTikTokServerEvent((string) ($row['event'] ?? 'PageView'));
$testCode = trim((string) ($row['test_code'] ?? ''));
$payload = [
'pixel_code' => $pixelId,
'event' => $event,
'event_id' => uniqid('split_', true),
'timestamp' => gmdate('c'),
'context' => array_filter([
'ip' => $clientIp !== '' ? $clientIp : null,
'user_agent' => $userAgent !== '' ? $userAgent : null,
'page' => $eventSourceUrl !== '' ? ['url' => $eventSourceUrl] : null,
]),
];
if ($testCode !== '') {
$payload['test_event_code'] = $testCode;
}
$url = 'https://business-api.tiktok.com/open_api/v1.3/event/track/';
self::postJson($url, $payload, [
'Access-Token: ' . $accessToken,
'Content-Type: application/json',
], 'tiktok', $pixelId);
}
private static function mapTikTokServerEvent(string $event): string
{
$map = [
'PageView' => 'Pageview',
'Lead' => 'SubmitForm',
'Contact' => 'Contact',
'AddToCart' => 'AddToCart',
'Purchase' => 'CompletePayment',
'Subscribe' => 'Subscribe',
];
return $map[$event] ?? 'Pageview';
}
/**
* @param array<string, mixed> $payload
* @param array<int, string> $headers
*/
private static function postJson(string $url, array $payload, array $headers, string $platform, string $pixelId): void
{
$json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if ($json === false) {
return;
}
$ch = curl_init($url);
if ($ch === false) {
return;
}
$defaultHeaders = ['Content-Type: application/json'];
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $json,
CURLOPT_HTTPHEADER => array_merge($defaultHeaders, $headers),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 3,
CURLOPT_CONNECTTIMEOUT => 2,
]);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($errno !== 0 || ($httpCode >= 400 && $httpCode !== 0)) {
Log::error(sprintf(
'Split pixel postback failed platform=%s pixel=%s http=%d curl=%d',
$platform,
$pixelId,
$httpCode,
$errno
));
} elseif (is_string($response) && strpos($response, '"error"') !== false) {
Log::error(sprintf('Split pixel postback api error platform=%s pixel=%s', $platform, $pixelId));
}
}
}