EvoplayService.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. <?php
  2. namespace App\Game\Services;
  3. use App\Game\GlobalUserInfo;
  4. use App\Http\helper\NumConfig;
  5. use App\Models\AccountsInfo;
  6. use App\Notification\TelegramBot;
  7. use App\Util;
  8. use GuzzleHttp\Client;
  9. use GuzzleHttp\Exception\RequestException;
  10. use Illuminate\Support\Facades\DB;
  11. use Illuminate\Support\Facades\Log;
  12. use Illuminate\Support\Facades\Redis;
  13. class EvoplayService
  14. {
  15. protected $client;
  16. protected $currency;
  17. protected $apiUrl;
  18. protected $projectId;
  19. protected $secretKey;
  20. protected $version;
  21. protected $systemId;
  22. public function __construct()
  23. {
  24. $this->client = new Client();
  25. $this->currency = env('CONFIG_24680_CURRENCY');
  26. $this->apiUrl = env('EVOPLAY_API_URL', 'https://api.evoplay.games');
  27. $this->projectId = env('EVOPLAY_PROJECT_ID', '11800');
  28. $this->secretKey = env('EVOPLAY_SECRET_KEY', '68e071e88a198e25d847c30d0724eb2c');
  29. $this->version = env('EVOPLAY_API_VERSION', '1');
  30. $this->systemId = env('EVOPLAY_SYSTEM_ID', '24680-com-test');
  31. }
  32. /**
  33. * 调用子API
  34. *
  35. * @param string $username
  36. * @param \Illuminate\Http\Request $request
  37. * @return \Illuminate\Http\JsonResponse
  38. */
  39. public function callSubApi($username, $request)
  40. {
  41. Util::WriteLog('evoplay', 'callsubapi');
  42. $apiurl = ServerService::GetApiByGUID($username);
  43. try {
  44. $postData = $request->post();
  45. $response = $this->client->post($apiurl . $_SERVER['REQUEST_URI'], [
  46. 'verify' => false,
  47. 'form_params' => $postData,
  48. ]);
  49. $res = json_decode($response->getBody(), true);
  50. Util::WriteLog('evoplay', $res);
  51. return $res;
  52. } catch (RequestException $e) {
  53. return $this->handleRequestException($e);
  54. }
  55. }
  56. /**
  57. * 处理请求异常
  58. *
  59. * @param \Exception $e
  60. * @return array
  61. */
  62. private function handleRequestException(\Exception $e)
  63. {
  64. Util::WriteLog('evoplay_error', $e->getMessage());
  65. return [
  66. 'status' => 'error',
  67. 'error' => [
  68. 'code' => 'INTERNAL_ERROR',
  69. 'message' => 'Internal server error'
  70. ]
  71. ];
  72. }
  73. /**
  74. * 生成签名
  75. *
  76. * @param array $data 请求数据
  77. * @return string 签名
  78. */
  79. public function generateSignature($data)
  80. {
  81. // 按字母顺序排序参数
  82. ksort($data);
  83. // 将参数转换为查询字符串
  84. $query = http_build_query($data);
  85. // 生成签名
  86. return hash_hmac('sha256', $query, $this->secretKey);
  87. }
  88. /**
  89. * 获取游戏列表
  90. *
  91. * @return array
  92. */
  93. public function getGamesList()
  94. {
  95. try {
  96. $data = [
  97. 'project' => $this->projectId,
  98. 'version' => $this->version,
  99. 'need_extra_data' => 1,
  100. ];
  101. // 生成签名
  102. $data['signature'] = $this->generateSignature($data);
  103. // 调用游戏列表API
  104. $response = $this->client->post($this->apiUrl . '/Game/getList', [
  105. 'verify' => false,
  106. 'form_params' => $data
  107. ]);
  108. return json_decode($response->getBody(), true);
  109. } catch (\Exception $e) {
  110. Util::WriteLog('evoplay_error', $e->getMessage());
  111. return ['error' => $e->getMessage()];
  112. }
  113. }
  114. /**
  115. * 获取游戏启动URL
  116. *
  117. * @param string $gameId 游戏ID
  118. * @param string $userId 用户ID
  119. * @param string $language 语言
  120. * @param string $currency 货币
  121. * @param bool $demo 是否为试玩模式
  122. * @return string|null
  123. */
  124. public function getGameUrl($gameId, $userId, $language = 'en', $currency = null, $demo = false)
  125. {
  126. try {
  127. if (!$currency) {
  128. $currency = $this->currency;
  129. }
  130. $data = [
  131. 'project_id' => $this->projectId,
  132. 'version' => $this->version,
  133. 'system_id' => $this->systemId,
  134. 'user_id' => $userId,
  135. 'game_id' => $gameId,
  136. 'currency' => $currency,
  137. 'language' => $language,
  138. 'denomination' => 1,
  139. 'return_url' => url('/'),
  140. 'callback_url' => url('/api/callback/evoplay'),
  141. 'callback_version' => 2,
  142. 'signature' => ''
  143. ];
  144. if ($demo) {
  145. $data['demo'] = 1;
  146. }
  147. // 生成签名
  148. $data['signature'] = $this->generateSignature($data);
  149. // 调用游戏启动API
  150. $response = $this->client->post($this->apiUrl . '/game/geturl', [
  151. 'verify' => false,
  152. 'form_params' => $data
  153. ]);
  154. $result = json_decode($response->getBody(), true);
  155. if (isset($result['status']) && $result['status'] === 'ok' && isset($result['url'])) {
  156. return $result['url'];
  157. }
  158. Util::WriteLog('evoplay_error', 'Failed to get game URL: ' . json_encode($result));
  159. return null;
  160. } catch (\Exception $e) {
  161. Util::WriteLog('evoplay_error', $e->getMessage());
  162. return null;
  163. }
  164. }
  165. /**
  166. * 玩家验证
  167. *
  168. * @param string $userId 用户ID
  169. * @return array 用户信息
  170. */
  171. public function validateUser($userId)
  172. {
  173. try {
  174. // 获取用户信息
  175. $user = GlobalUserInfo::getGameUserInfo('GlobalUID', $userId);
  176. if (!$user) {
  177. return [
  178. 'status' => 'error',
  179. 'error' => [
  180. 'code' => 'USER_NOT_FOUND',
  181. 'message' => 'User not found'
  182. ]
  183. ];
  184. }
  185. // 获取用户余额
  186. $balance = GlobalUserInfo::getScoreByUserID($user->UserID);
  187. return [
  188. 'status' => 'ok',
  189. 'data' => [
  190. 'id' => $userId,
  191. 'name' => $user->NickName,
  192. 'balance' => $balance,
  193. 'currency' => $this->currency
  194. ]
  195. ];
  196. } catch (\Exception $e) {
  197. Util::WriteLog('evoplay_error', $e->getMessage());
  198. return [
  199. 'status' => 'error',
  200. 'error' => [
  201. 'code' => 'INTERNAL_ERROR',
  202. 'message' => 'Internal server error'
  203. ]
  204. ];
  205. }
  206. }
  207. /**
  208. * 处理余额查询
  209. *
  210. * @param string $userId 用户ID
  211. * @return array 余额信息
  212. */
  213. public function getBalance($userId)
  214. {
  215. try {
  216. // 获取用户信息
  217. $user = GlobalUserInfo::getGameUserInfo('GlobalUID', $userId);
  218. if (!$user) {
  219. return [
  220. 'status' => 'error',
  221. 'error' => [
  222. 'code' => 'USER_NOT_FOUND',
  223. 'message' => 'User not found'
  224. ]
  225. ];
  226. }
  227. // 获取用户余额
  228. $balance = GlobalUserInfo::getScoreByUserID($user->UserID);
  229. return [
  230. 'status' => 'ok',
  231. 'data' => [
  232. 'balance' => $balance,
  233. 'currency' => $this->currency
  234. ]
  235. ];
  236. } catch (\Exception $e) {
  237. Util::WriteLog('evoplay_error', $e->getMessage());
  238. return [
  239. 'status' => 'error',
  240. 'error' => [
  241. 'code' => 'INTERNAL_ERROR',
  242. 'message' => 'Internal server error'
  243. ]
  244. ];
  245. }
  246. }
  247. /**
  248. * 处理下注请求
  249. *
  250. * @param string $userId 用户ID
  251. * @param string $gameId 游戏ID
  252. * @param float $amount 金额
  253. * @param string $transactionId 交易ID
  254. * @param string $roundId 回合ID
  255. * @return array 处理结果
  256. */
  257. public function bet($userId, $gameId, $amount, $transactionId, $roundId)
  258. {
  259. try {
  260. // 获取用户信息
  261. $user = GlobalUserInfo::getGameUserInfo('GlobalUID', $userId);
  262. if (!$user) {
  263. return [
  264. 'status' => 'error',
  265. 'error' => [
  266. 'code' => 'USER_NOT_FOUND',
  267. 'message' => 'User not found'
  268. ]
  269. ];
  270. }
  271. $userId = $user->UserID;
  272. // 检查交易是否已处理
  273. $transactionKey = 'evoplay_tx_' . $transactionId;
  274. if (Redis::exists($transactionKey)) {
  275. // 交易已处理,返回上次的结果
  276. $previousResult = json_decode(Redis::get($transactionKey), true);
  277. return $previousResult;
  278. }
  279. // 获取当前余额
  280. $currentBalance = GlobalUserInfo::getScoreByUserID($userId) * NumConfig::NUM_VALUE;
  281. $amountInCents = intval($amount * NumConfig::NUM_VALUE);
  282. // 检查余额是否足够
  283. if ($currentBalance < $amountInCents) {
  284. $errorResponse = [
  285. 'status' => 'error',
  286. 'error' => [
  287. 'code' => 'INSUFFICIENT_FUNDS',
  288. 'message' => 'Insufficient funds'
  289. ]
  290. ];
  291. return $errorResponse;
  292. }
  293. // 获取独占锁确保事务安全
  294. $res = SetNXLock::getExclusiveLock('evoplay_bet_' . $transactionId, 86400);
  295. if (!$res) {
  296. // 已经处理过,返回之前的结果
  297. return $this->getBalance($userId);
  298. }
  299. // 扣除余额
  300. DB::connection('write')->table('QPTreasureDB.dbo.GameScoreInfo')
  301. ->where('UserID', $userId)
  302. ->decrement('Score', $amountInCents);
  303. // 记录下注
  304. PlatformService::platformBet('evoplay', $gameId, $amountInCents, $userId);
  305. // 保存交易数据用于可能的取消
  306. Redis::set('evoplay_bet_' . $transactionId, $gameId . '_' . $amountInCents . '_' . $roundId);
  307. Redis::expire('evoplay_bet_' . $transactionId, 86400);
  308. // 更新当前余额
  309. $currentBalance -= $amountInCents;
  310. // 构造成功响应
  311. $response = [
  312. 'status' => 'ok',
  313. 'data' => [
  314. 'balance' => $currentBalance / NumConfig::NUM_VALUE,
  315. 'currency' => $this->currency,
  316. 'transaction_id' => $transactionId
  317. ]
  318. ];
  319. // 缓存交易结果
  320. Redis::set($transactionKey, json_encode($response));
  321. Redis::expire($transactionKey, 86400);
  322. return $response;
  323. } catch (\Exception $e) {
  324. Util::WriteLog('evoplay_error', $e->getMessage());
  325. return [
  326. 'status' => 'error',
  327. 'error' => [
  328. 'code' => 'INTERNAL_ERROR',
  329. 'message' => 'Internal server error'
  330. ]
  331. ];
  332. }
  333. }
  334. /**
  335. * 处理赢钱请求
  336. *
  337. * @param string $userId 用户ID
  338. * @param string $gameId 游戏ID
  339. * @param float $amount 金额
  340. * @param string $transactionId 交易ID
  341. * @param string $roundId 回合ID
  342. * @param string $betTransactionId 下注交易ID
  343. * @return array 处理结果
  344. */
  345. public function win($userId, $gameId, $amount, $transactionId, $roundId, $betTransactionId = null)
  346. {
  347. try {
  348. // 获取用户信息
  349. $user = GlobalUserInfo::getGameUserInfo('GlobalUID', $userId);
  350. if (!$user) {
  351. return [
  352. 'status' => 'error',
  353. 'error' => [
  354. 'code' => 'USER_NOT_FOUND',
  355. 'message' => 'User not found'
  356. ]
  357. ];
  358. }
  359. $userId = $user->UserID;
  360. // 检查交易是否已处理
  361. $transactionKey = 'evoplay_tx_' . $transactionId;
  362. if (Redis::exists($transactionKey)) {
  363. // 交易已处理,返回上次的结果
  364. $previousResult = json_decode(Redis::get($transactionKey), true);
  365. return $previousResult;
  366. }
  367. // 获取当前余额
  368. $currentBalance = GlobalUserInfo::getScoreByUserID($userId) * NumConfig::NUM_VALUE;
  369. $amountInCents = intval($amount * NumConfig::NUM_VALUE);
  370. // 获取独占锁确保事务安全
  371. $res = SetNXLock::getExclusiveLock('evoplay_win_' . $transactionId, 86400);
  372. if (!$res) {
  373. // 已经处理过,返回之前的结果
  374. return $this->getBalance($userId);
  375. }
  376. // 检查投注记录
  377. $originalBet = 0;
  378. if ($betTransactionId) {
  379. $betKey = 'evoplay_bet_' . $betTransactionId;
  380. if (Redis::exists($betKey)) {
  381. $betInfo = Redis::get($betKey);
  382. $parts = explode('_', $betInfo);
  383. $originalBet = isset($parts[1]) ? intval($parts[1]) : 0;
  384. }
  385. }
  386. // 增加余额
  387. DB::connection('write')->table('QPTreasureDB.dbo.GameScoreInfo')
  388. ->where('UserID', $userId)
  389. ->increment('Score', $amountInCents);
  390. // 记录赢取
  391. PlatformService::platformWin(
  392. $userId,
  393. 'evoplay',
  394. $gameId,
  395. $amountInCents,
  396. $originalBet,
  397. $currentBalance + $amountInCents
  398. );
  399. // 更新当前余额
  400. $currentBalance += $amountInCents;
  401. // 构造成功响应
  402. $response = [
  403. 'status' => 'ok',
  404. 'data' => [
  405. 'balance' => $currentBalance / NumConfig::NUM_VALUE,
  406. 'currency' => $this->currency,
  407. 'transaction_id' => $transactionId
  408. ]
  409. ];
  410. // 缓存交易结果
  411. Redis::set($transactionKey, json_encode($response));
  412. Redis::expire($transactionKey, 86400);
  413. return $response;
  414. } catch (\Exception $e) {
  415. Util::WriteLog('evoplay_error', $e->getMessage());
  416. return [
  417. 'status' => 'error',
  418. 'error' => [
  419. 'code' => 'INTERNAL_ERROR',
  420. 'message' => 'Internal server error'
  421. ]
  422. ];
  423. }
  424. }
  425. /**
  426. * 处理交易回滚请求
  427. *
  428. * @param string $userId 用户ID
  429. * @param string $transactionId 要回滚的交易ID
  430. * @param string $rollbackTransactionId 回滚交易ID
  431. * @return array 处理结果
  432. */
  433. public function rollback($userId, $transactionId, $rollbackTransactionId)
  434. {
  435. try {
  436. // 获取用户信息
  437. $user = GlobalUserInfo::getGameUserInfo('GlobalUID', $userId);
  438. if (!$user) {
  439. return [
  440. 'status' => 'error',
  441. 'error' => [
  442. 'code' => 'USER_NOT_FOUND',
  443. 'message' => 'User not found'
  444. ]
  445. ];
  446. }
  447. $userId = $user->UserID;
  448. // 检查回滚交易是否已处理
  449. $rollbackKey = 'evoplay_rollback_' . $rollbackTransactionId;
  450. if (Redis::exists($rollbackKey)) {
  451. // 回滚已处理,返回上次的结果
  452. $previousResult = json_decode(Redis::get($rollbackKey), true);
  453. return $previousResult;
  454. }
  455. // 检查原始交易
  456. $transactionKey = 'evoplay_tx_' . $transactionId;
  457. if (!Redis::exists($transactionKey)) {
  458. return [
  459. 'status' => 'error',
  460. 'error' => [
  461. 'code' => 'TRANSACTION_NOT_FOUND',
  462. 'message' => 'Original transaction not found'
  463. ]
  464. ];
  465. }
  466. // 获取原始交易信息
  467. $originalTransaction = json_decode(Redis::get($transactionKey), true);
  468. // 检查是否是投注交易
  469. $betKey = 'evoplay_bet_' . $transactionId;
  470. if (Redis::exists($betKey)) {
  471. $betInfo = Redis::get($betKey);
  472. $parts = explode('_', $betInfo);
  473. $gameId = $parts[0] ?? '';
  474. $amount = isset($parts[1]) ? intval($parts[1]) : 0;
  475. // 获取独占锁确保事务安全
  476. $res = SetNXLock::getExclusiveLock($rollbackKey, 86400);
  477. if (!$res) {
  478. // 已经处理过,返回之前的结果
  479. return $this->getBalance($userId);
  480. }
  481. // 获取当前余额
  482. $currentBalance = GlobalUserInfo::getScoreByUserID($userId) * NumConfig::NUM_VALUE;
  483. // 退还金额
  484. DB::connection('write')->table('QPTreasureDB.dbo.GameScoreInfo')
  485. ->where('UserID', $userId)
  486. ->increment('Score', $amount);
  487. // 记录取消交易
  488. PlatformService::platformBet('evoplay', $gameId, -$amount, $userId);
  489. // 更新当前余额
  490. $currentBalance += $amount;
  491. // 构造成功响应
  492. $response = [
  493. 'status' => 'ok',
  494. 'data' => [
  495. 'balance' => $currentBalance / NumConfig::NUM_VALUE,
  496. 'currency' => $this->currency,
  497. 'transaction_id' => $rollbackTransactionId
  498. ]
  499. ];
  500. // 缓存回滚结果
  501. Redis::set($rollbackKey, json_encode($response));
  502. Redis::expire($rollbackKey, 86400);
  503. return $response;
  504. } else {
  505. // 如果不是投注交易,则可能是赢钱交易,处理方式类似
  506. // 这里简化处理,实际应该根据Evoplay的具体要求实现
  507. return [
  508. 'status' => 'error',
  509. 'error' => [
  510. 'code' => 'UNSUPPORTED_OPERATION',
  511. 'message' => 'Only bet transactions can be rolled back'
  512. ]
  513. ];
  514. }
  515. } catch (\Exception $e) {
  516. Util::WriteLog('evoplay_error', $e->getMessage());
  517. return [
  518. 'status' => 'error',
  519. 'error' => [
  520. 'code' => 'INTERNAL_ERROR',
  521. 'message' => 'Internal server error'
  522. ]
  523. ];
  524. }
  525. }
  526. }