FacebookEventService.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. <?php
  2. namespace App\Game\Services;
  3. use App\Game\GlobalUserInfo;
  4. use App\Game\WebChannelConfig;
  5. use App\Http\helper\HttpCurl;
  6. use App\Jobs\FacebookServerEvent;
  7. use Illuminate\Http\Request;
  8. use Illuminate\Support\Facades\Log;
  9. class FacebookEventService
  10. {
  11. /**
  12. * 通过服务端 Facebook Conversions API 上报事件
  13. *
  14. * 去重策略(前后端统一):
  15. * 仅依赖 Facebook 官方的 event_id 去重:
  16. * - 前端 fbq 上报时必须使用同一个 event_id
  17. * - 服务端调用 Conversions API 时也传入同一个 event_id
  18. * - Facebook 会自动把 Browser + Server 事件按照 event_id 合并
  19. *
  20. * @param string $eventName
  21. * @param string $eventId
  22. * @param int|string $userId
  23. * @param float|int|null $value
  24. * @param string|null $currency
  25. * @param string $pixelId
  26. * @param string $accessToken
  27. * @param array $extraCustomData
  28. */
  29. public static function trackEvent(
  30. string $eventName,
  31. string $eventId,
  32. $userId,
  33. $value = null,
  34. ?string $currency = null,
  35. string $pixelId = '',
  36. string $accessToken = '',
  37. array $extraCustomData = []
  38. ): void {
  39. if (empty($pixelId) || empty($accessToken)) {
  40. return;
  41. }
  42. $eventTime = time();
  43. $externalId = (string)$userId;
  44. // Facebook Conversions API 要求 user_data 做哈希,这里只对 external_id 做一次 sha256
  45. $userData = [
  46. 'external_id' => hash('sha256', $externalId),
  47. ];
  48. $customData = array_merge(
  49. array_filter([
  50. 'currency' => $currency,
  51. 'value' => $value,
  52. ], static function ($v) {
  53. return $v !== null && $v !== '';
  54. }),
  55. $extraCustomData
  56. );
  57. $payload = [
  58. 'data' => [
  59. [
  60. 'event_name' => $eventName,
  61. 'event_time' => $eventTime,
  62. 'action_source' => 'website',
  63. 'event_id' => $eventId,
  64. 'user_data' => $userData,
  65. 'custom_data' => $customData,
  66. ],
  67. ],
  68. ];
  69. $url = sprintf(
  70. 'https://graph.facebook.com/v25.0/%s/events?access_token=%s',
  71. $pixelId,
  72. $accessToken
  73. );
  74. try {
  75. $http = new HttpCurl();
  76. $response = $http->curlPost($url, $payload, 'json', true);
  77. Log::info('FacebookEventService trackEvent success', [
  78. 'event_name' => $eventName,
  79. 'event_id' => $eventId,
  80. 'user_id' => $userId,
  81. 'pixel_id' => $pixelId,
  82. 'payload' => $payload,
  83. 'response' => $response,
  84. ]);
  85. } catch (\Throwable $e) {
  86. Log::error('FacebookEventService trackEvent error', [
  87. 'event_name' => $eventName,
  88. 'event_id' => $eventId,
  89. 'user_id' => $userId,
  90. 'pixel_id' => $pixelId,
  91. 'message' => $e->getMessage(),
  92. ]);
  93. }
  94. }
  95. /**
  96. * 注册完成事件(对应前端 CompleteRegistration)
  97. */
  98. public static function trackCompleteRegistration(GlobalUserInfo $user, WebChannelConfig $config): void
  99. {
  100. if (empty($config->PlatformID) || empty($config->PlatformToken)) {
  101. return;
  102. }
  103. $pixelId = $config->PlatformID;
  104. $accessToken = $config->PlatformToken;
  105. // 建议前端也使用相同的 event_id:reg_{UserID}
  106. $eventId = 'reg_' . $user->UserID;
  107. FacebookServerEvent::dispatch(
  108. 'CompleteRegistration',
  109. $eventId,
  110. $user->UserID,
  111. null,
  112. null,
  113. $pixelId,
  114. $accessToken
  115. );
  116. }
  117. /**
  118. * 支付事件(对应前端 pay + firstpayD0/firstpayD1/payagain/Purchase)
  119. *
  120. * @param int|string $userId
  121. * @param string $orderSn
  122. * @param float|int $amount
  123. * @param string $currency
  124. * @param bool $isFirst
  125. * @param bool $isD0
  126. * @param int $channel
  127. */
  128. public static function trackPayEvent(
  129. $userId,
  130. string $orderSn,
  131. $amount,
  132. string $currency,
  133. bool $isFirst,
  134. bool $isD0,
  135. int $channel
  136. ): void {
  137. $config = WebChannelConfig::getByChannel($channel);
  138. if (!$config || empty($config->PlatformID) || empty($config->PlatformToken)) {
  139. return;
  140. }
  141. $pixelId = $config->PlatformID;
  142. $accessToken = $config->PlatformToken;
  143. // 统一使用同一个 event_id(与前端保持一致)
  144. // 前端所有支付相关事件共用:purchase_{order_sn}
  145. $eventId = 'purchase_' . $orderSn;
  146. // 标准 Purchase 事件(所有支付都会上报)
  147. FacebookServerEvent::dispatch(
  148. 'Purchase',
  149. $eventId,
  150. $userId,
  151. $amount,
  152. $currency,
  153. $pixelId,
  154. $accessToken
  155. );
  156. // 首次支付
  157. if ($isFirst) {
  158. FacebookServerEvent::dispatch(
  159. 'firstpayD1',
  160. $eventId,
  161. $userId,
  162. $amount,
  163. $currency,
  164. $pixelId,
  165. $accessToken
  166. );
  167. if ($isD0) {
  168. // D0 首充自定义事件
  169. FacebookServerEvent::dispatch(
  170. 'firstpayD0',
  171. $eventId,
  172. $userId,
  173. $amount,
  174. $currency,
  175. $pixelId,
  176. $accessToken
  177. );
  178. // D0 首充标准事件:AddToWishlist(与前端保持一致)
  179. FacebookServerEvent::dispatch(
  180. 'AddToWishlist',
  181. $eventId,
  182. $userId,
  183. $amount,
  184. $currency,
  185. $pixelId,
  186. $accessToken
  187. );
  188. }
  189. } elseif (!$isD0) {
  190. // 复充(非首次且非 D0)
  191. FacebookServerEvent::dispatch(
  192. 'payagain',
  193. $eventId,
  194. $userId,
  195. $amount,
  196. $currency,
  197. $pixelId,
  198. $accessToken
  199. );
  200. }
  201. }
  202. }