Explorar el Código

添加fb服务端上报逻辑

Tree hace 1 día
padre
commit
85df49bc5f

+ 222 - 0
app/Game/Services/FacebookEventService.php

@@ -0,0 +1,222 @@
+<?php
+
+namespace App\Game\Services;
+
+use App\Game\GlobalUserInfo;
+use App\Game\WebChannelConfig;
+use App\Http\helper\HttpCurl;
+use App\Jobs\FacebookServerEvent;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+
+class FacebookEventService
+{
+    /**
+     * 通过服务端 Facebook Conversions API 上报事件
+     *
+     * 去重策略(前后端统一):
+     * 仅依赖 Facebook 官方的 event_id 去重:
+     * - 前端 fbq 上报时必须使用同一个 event_id
+     * - 服务端调用 Conversions API 时也传入同一个 event_id
+     * - Facebook 会自动把 Browser + Server 事件按照 event_id 合并
+     *
+     * @param string $eventName
+     * @param string $eventId
+     * @param int|string $userId
+     * @param float|int|null $value
+     * @param string|null $currency
+     * @param string $pixelId
+     * @param string $accessToken
+     * @param array $extraCustomData
+     */
+    public static function trackEvent(
+        string $eventName,
+        string $eventId,
+        $userId,
+        $value = null,
+        ?string $currency = null,
+        string $pixelId = '',
+        string $accessToken = '',
+        array $extraCustomData = []
+    ): void {
+        if (empty($pixelId) || empty($accessToken)) {
+            return;
+        }
+
+        $eventTime = time();
+        $externalId = (string)$userId;
+
+        // Facebook Conversions API 要求 user_data 做哈希,这里只对 external_id 做一次 sha256
+        $userData = [
+            'external_id' => hash('sha256', $externalId),
+        ];
+
+        $customData = array_merge(
+            array_filter([
+                'currency' => $currency,
+                'value'    => $value,
+            ], static function ($v) {
+                return $v !== null && $v !== '';
+            }),
+            $extraCustomData
+        );
+
+        $payload = [
+            'data' => [
+                [
+                    'event_name'    => $eventName,
+                    'event_time'    => $eventTime,
+                    'action_source' => 'website',
+                    'event_id'      => $eventId,
+                    'user_data'     => $userData,
+                    'custom_data'   => $customData,
+                ],
+            ],
+        ];
+
+        $url = sprintf(
+            'https://graph.facebook.com/v25.0/%s/events?access_token=%s',
+            $pixelId,
+            $accessToken
+        );
+
+        try {
+            $http = new HttpCurl();
+            $response = $http->curlPost($url, $payload, 'json', true);
+
+            Log::info('FacebookEventService trackEvent success', [
+                'event_name' => $eventName,
+                'event_id'   => $eventId,
+                'user_id'    => $userId,
+                'pixel_id'   => $pixelId,
+                'payload'    => $payload,
+                'response'   => $response,
+            ]);
+        } catch (\Throwable $e) {
+            Log::error('FacebookEventService trackEvent error', [
+                'event_name' => $eventName,
+                'event_id'   => $eventId,
+                'user_id'    => $userId,
+                'pixel_id'   => $pixelId,
+                'message'    => $e->getMessage(),
+            ]);
+        }
+    }
+
+    /**
+     * 注册完成事件(对应前端 CompleteRegistration)
+     */
+    public static function trackCompleteRegistration(GlobalUserInfo $user, WebChannelConfig $config): void
+    {
+        if (empty($config->PlatformID) || empty($config->PlatformToken)) {
+            return;
+        }
+        $pixelId = $config->PlatformID;
+        $accessToken = $config->PlatformToken;
+
+        // 建议前端也使用相同的 event_id:reg_{UserID}
+        $eventId = 'reg_' . $user->UserID;
+
+        FacebookServerEvent::dispatch(
+            'CompleteRegistration',
+            $eventId,
+            $user->UserID,
+            null,
+            null,
+            $pixelId,
+            $accessToken
+        );
+    }
+
+    /**
+     * 支付事件(对应前端 pay + firstpayD0/firstpayD1/payagain/Purchase)
+     *
+     * @param int|string $userId
+     * @param string $orderSn
+     * @param float|int $amount
+     * @param string $currency
+     * @param bool $isFirst
+     * @param bool $isD0
+     * @param int $channel
+     */
+    public static function trackPayEvent(
+        $userId,
+        string $orderSn,
+        $amount,
+        string $currency,
+        bool $isFirst,
+        bool $isD0,
+        int $channel
+    ): void {
+        $config = WebChannelConfig::getByChannel($channel);
+        if (!$config || empty($config->PlatformID) || empty($config->PlatformToken)) {
+            return;
+        }
+        $pixelId = $config->PlatformID;
+        $accessToken = $config->PlatformToken;
+
+        // 统一使用同一个 event_id(与前端保持一致)
+        // 前端所有支付相关事件共用:purchase_{order_sn}
+        $eventId = 'purchase_' . $orderSn;
+
+        // 标准 Purchase 事件(所有支付都会上报)
+        FacebookServerEvent::dispatch(
+            'Purchase',
+            $eventId,
+            $userId,
+            $amount,
+            $currency,
+            $pixelId,
+            $accessToken
+        );
+
+        // 首次支付
+        if ($isFirst) {
+            FacebookServerEvent::dispatch(
+                'firstpayD1',
+                $eventId,
+                $userId,
+                $amount,
+                $currency,
+                $pixelId,
+                $accessToken
+            );
+
+            if ($isD0) {
+                // D0 首充自定义事件
+                FacebookServerEvent::dispatch(
+                    'firstpayD0',
+                    $eventId,
+                    $userId,
+                    $amount,
+                    $currency,
+                    $pixelId,
+                    $accessToken
+                );
+
+                // D0 首充标准事件:AddToWishlist(与前端保持一致)
+                FacebookServerEvent::dispatch(
+                    'AddToWishlist',
+                    $eventId,
+                    $userId,
+                    $amount,
+                    $currency,
+                    $pixelId,
+                    $accessToken
+                );
+            }
+        } elseif (!$isD0) {
+            // 复充(非首次且非 D0)
+            FacebookServerEvent::dispatch(
+                'payagain',
+                $eventId,
+                $userId,
+                $amount,
+                $currency,
+                $pixelId,
+                $accessToken
+            );
+        }
+    }
+}
+

+ 15 - 1
app/Game/WebChannelConfig.php

@@ -22,7 +22,21 @@ class WebChannelConfig extends Model
     protected $keyType = 'int';
     protected $connection = 'mysql';
     protected $fillable = [
-        'Channel', 'PackageName', 'RegionID', 'Remarks', 'StateNo','SpecialMode', 'PlatformName', 'PlatformID','LoginOpen','RegOpen','BonusArr','ShadowChannel','LightApk','FullApk'
+        'Channel',
+        'PackageName',
+        'RegionID',
+        'Remarks',
+        'StateNo',
+        'SpecialMode',
+        'PlatformName',
+        'PlatformID',
+        'LoginOpen',
+        'RegOpen',
+        'BonusArr',
+        'ShadowChannel',
+        'LightApk',
+        'FullApk',
+        'PlatformToken',      // 每个渠道/像素对应的 FB Conversions API token
     ];
 
     private static $key='web_channel_config:';

+ 8 - 0
app/Http/Controllers/Game/LoginController.php

@@ -9,6 +9,7 @@ use App\Game\QuickAccountPass;
 use App\Game\QuickAccountPassStore;
 use App\Game\RePayConfig;
 use App\Game\Services\AgentService;
+use App\Game\Services\FacebookEventService;
 use App\Game\Services\OuroGameService;
 use App\Game\Services\RouteService;
 use App\Game\WebChannelConfig;
@@ -1443,6 +1444,13 @@ class LoginController extends Controller
 
         AccountsInfo::where('UserID', $UserID)->update(['UserMedal' => $defaultGameId ]);
 
+        try {
+            FacebookEventService::trackCompleteRegistration($globalUserInfo,$config);
+        }catch (\Exception $e){
+
+        }
+
+
         Util::WriteLog('register_params',[$request,$guser]);
         return response()->json(apiReturnSuc($guser, ['reg.success', 'Registro realizado com sucesso!']));//->withCookie($this->setLoginCookie($guser['sign']));
     }

+ 83 - 0
app/Jobs/FacebookServerEvent.php

@@ -0,0 +1,83 @@
+<?php
+
+namespace App\Jobs;
+
+use App\Game\Services\FacebookEventService;
+use Illuminate\Bus\Queueable;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Bus\Dispatchable;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Support\Facades\Log;
+
+class FacebookServerEvent implements ShouldQueue
+{
+    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
+
+    protected string $eventName;
+    protected string $eventId;
+    protected $userId;
+    protected $value;
+    protected ?string $currency;
+    protected array $extraCustomData;
+    protected string $pixelId;
+    protected string $accessToken;
+
+    public $tries = 3;
+    public $timeout = 30;
+
+    /**
+     * @param string $eventName
+     * @param string $eventId
+     * @param int|string $userId
+     * @param float|int|null $value
+     * @param string|null $currency
+     * @param string $pixelId
+     * @param string $accessToken
+     * @param array $extraCustomData
+     */
+    public function __construct(
+        string $eventName,
+        string $eventId,
+        $userId,
+        $value = null,
+        ?string $currency = null,
+        string $pixelId = '',
+        string $accessToken = '',
+        array $extraCustomData = []
+    ) {
+        $this->eventName = $eventName;
+        $this->eventId = $eventId;
+        $this->userId = $userId;
+        $this->value = $value;
+        $this->currency = $currency;
+        $this->extraCustomData = $extraCustomData;
+        $this->pixelId = $pixelId;
+        $this->accessToken = $accessToken;
+    }
+
+    public function handle(): void
+    {
+        try {
+            // 队列里没有 Request,上报时不依赖 Request,只依赖渠道配置中的 PixelID
+            FacebookEventService::trackEvent(
+                $this->eventName,
+                $this->eventId,
+                $this->userId,
+                $this->value,
+                $this->currency,
+                $this->pixelId,
+                $this->accessToken,
+                $this->extraCustomData
+            );
+        } catch (\Throwable $e) {
+            Log::error('FacebookServerEvent job error', [
+                'event_name' => $this->eventName,
+                'event_id'   => $this->eventId,
+                'user_id'    => $this->userId,
+                'message'    => $e->getMessage(),
+            ]);
+        }
+    }
+}
+

+ 16 - 0
app/Jobs/Order.php

@@ -9,6 +9,7 @@ use App\Models\PrivateMail;
 use App\Services\Custom;
 use App\Services\FirstPayStatService;
 use App\Services\OrderServices;
+use App\Game\Services\FacebookEventService;
 use Illuminate\Bus\Queueable;
 use Illuminate\Contracts\Queue\ShouldQueue;
 use Illuminate\Foundation\Bus\Dispatchable;
@@ -100,6 +101,21 @@ class Order implements ShouldQueue
             //通知24680
             OuroGameService::notifyWebHall($user_id,"","pay_finish",["Golds"=>$Score,"PayNum"=>$favorable_price,"event"=>"pay","params"=>$item]);
 
+            try {
+                FacebookEventService::trackPayEvent(
+                    $user_id,
+                    $order_sn,
+                    $payAmt,
+                    'USD',
+                    $isfirst?1:0,
+                    (time()-strtotime($user->RegisterDate)<86400),
+                    $user->Channel
+                );
+            }catch (\Exception $e){
+
+            }
+
+
 //            OuroGameService::notifyWebHall($user_id,"",'call_client',["type"=>"track","event"=>"pay","params"=>$item]);
 
             $data = [];