diff --git a/app/common/service/GameBetSettleService.php b/app/common/service/GameBetSettleService.php index 648a745..6e217ba 100644 --- a/app/common/service/GameBetSettleService.php +++ b/app/common/service/GameBetSettleService.php @@ -171,8 +171,7 @@ final class GameBetSettleService } if (bccomp($win, '0', 2) > 0) { - $streakAtBet = (int) ($bet['streak_at_bet'] ?? 0); - $isJackpotTier = StreakWinReward::isJackpotForStreakAtBet($streakAtBet); + $jackpotFlags = self::betWinJackpotFlagsForOrder($bet, $win, $needReview); if (!isset($winByUser[$userId])) { $winByUser[$userId] = [ 'user_id' => $userId, @@ -182,12 +181,16 @@ final class GameBetSettleService 'total_win' => '0.00', 'balance_after' => $balanceAfter, 'is_jackpot' => false, + 'payout_pending_review' => false, 'bets' => [], ]; } - if ($isJackpotTier) { + if ($jackpotFlags['is_jackpot']) { $winByUser[$userId]['is_jackpot'] = true; } + if ($jackpotFlags['payout_pending_review']) { + $winByUser[$userId]['payout_pending_review'] = true; + } $winByUser[$userId]['total_win'] = bcadd($winByUser[$userId]['total_win'], $win, 2); $winByUser[$userId]['balance_after'] = $balanceAfter; $winByUser[$userId]['bets'][] = [ @@ -263,6 +266,7 @@ final class GameBetSettleService 'total_win' => (string) ($agg['total_win'] ?? '0.00'), 'balance_after' => (string) ($agg['balance_after'] ?? '0'), 'is_jackpot' => $isJackpotTier, + 'payout_pending_review' => false, 'bets' => [], ]; Log::warning('bet.win fallback emitted for settled winner', [ @@ -328,9 +332,11 @@ final class GameBetSettleService continue; } $isJackpot = !empty($payload['is_jackpot']); + $payoutPendingReview = !empty($payload['payout_pending_review']); $data = GameWebSocketPayloadHelper::mergeUserStreakInto(array_merge($payload, [ 'is_jackpot' => $isJackpot, 'is_win' => true, + 'payout_pending_review' => $payoutPendingReview, 'server_time' => $now, ]), $userId); GameWebSocketEventBus::publish(self::TOPIC_BET_WIN, $data); @@ -368,6 +374,9 @@ final class GameBetSettleService if ($userId === false || $userId <= 0) { continue; } + $orderStatus = filter_var($bet['status'] ?? 0, FILTER_VALIDATE_INT); + $needReview = $orderStatus === self::PLAY_STATUS_PENDING_REVIEW; + $jackpotFlags = self::betWinJackpotFlagsForOrder($bet, $win, $needReview); if (!isset($winByUser[$userId])) { $coin = Db::name('user')->where('id', $userId)->value('coin'); $winByUser[$userId] = [ @@ -378,12 +387,16 @@ final class GameBetSettleService 'total_win' => '0.00', 'balance_after' => (string) ($coin ?? '0'), 'is_jackpot' => false, + 'payout_pending_review' => false, 'bets' => [], ]; } - if (StreakWinReward::isJackpotForStreakAtBet((int) ($bet['streak_at_bet'] ?? 0))) { + if ($jackpotFlags['is_jackpot']) { $winByUser[$userId]['is_jackpot'] = true; } + if ($jackpotFlags['payout_pending_review']) { + $winByUser[$userId]['payout_pending_review'] = true; + } $winByUser[$userId]['total_win'] = bcadd((string) $winByUser[$userId]['total_win'], $win, 2); $betId = filter_var($bet['id'] ?? 0, FILTER_VALIDATE_INT); $winByUser[$userId]['bets'][] = [ @@ -396,7 +409,7 @@ final class GameBetSettleService } /** - * 大奖档命中时额外推送公共频道 jackpot.hit(与 bet.win 同一结算时刻,先后发出)。 + * 大奖档命中时额外推送公共频道 jackpot.hit(全站公告;个人中奖通知仍以 bet.win 为准,不可替代)。 * * @param list> $jackpotHits */ @@ -622,6 +635,7 @@ final class GameBetSettleService FILTER_VALIDATE_INT ); if ($periodId !== false && $periodId > 0 && $resultNumber !== false && $resultNumber > 0 && bccomp($winAmount, '0', 2) > 0) { + $jackpotFlags = self::betWinJackpotFlagsForOrder($row, $winAmount, false); self::publishBetWinsAfterCommit([[ 'user_id' => $userId, 'period_id' => $periodId, @@ -629,7 +643,8 @@ final class GameBetSettleService 'result_number' => $resultNumber, 'total_win' => $winAmount, 'balance_after' => is_string($balanceAfter ?? null) ? $balanceAfter : (string) (Db::name('user')->where('id', $userId)->value('coin') ?? '0'), - 'is_jackpot' => StreakWinReward::isJackpotForStreakAtBet((int) ($row['streak_at_bet'] ?? 0)), + 'is_jackpot' => $jackpotFlags['is_jackpot'], + 'payout_pending_review' => false, 'bets' => [['bet_id' => $playRecordId, 'win_amount' => $winAmount]], ]], $periodId); } @@ -902,4 +917,21 @@ final class GameBetSettleService } return bccomp($winAmount, $threshold, 2) >= 0; } + + /** + * bet.win 展示用大奖标记:连胜大奖档 或 触发后台大奖审核阈值,均视为「中大奖」并走 bet.win 通知。 + * + * @param array $bet + * @return array{is_jackpot: bool, payout_pending_review: bool} + */ + private static function betWinJackpotFlagsForOrder(array $bet, string $winAmount, bool $needReview): array + { + $streakJackpot = StreakWinReward::isJackpotForStreakAtBet((int) ($bet['streak_at_bet'] ?? 0)); + $amountJackpot = self::shouldRequireJackpotReview($winAmount, self::jackpotMaxAmount()); + + return [ + 'is_jackpot' => $streakJackpot || $amountJackpot || $needReview, + 'payout_pending_review' => $needReview, + ]; + } } diff --git a/docs/36字花-移动端接口设计草案.md b/docs/36字花-移动端接口设计草案.md index e250fec..0142894 100644 --- a/docs/36字花-移动端接口设计草案.md +++ b/docs/36字花-移动端接口设计草案.md @@ -840,8 +840,9 @@ - `data.total_win`:本期该用户派彩合计(已入账部分;若触发**后台大奖审核**(`win_amount >= game_config.jackpot_max_amount`)且注单为待审核,可能尚未入账,但仍会推送本事件) - `data.balance_after`:推送时用户余额(已派彩则为派彩后余额) - `data.bets[]`:`{ bet_id, win_amount }` 明细 - - **`data.is_jackpot`**:`bool`,`true` 表示该用户本期中奖注单含**大奖档**(`streak_win_reward` 中 `is_jackpot=true` 的档位,与下注时 `streak_at_bet` 对应),`false` 为普通档 + - **`data.is_jackpot`**:`bool`,`true` 表示**中大奖**(满足任一:连胜**大奖档**、或派彩金额达 `jackpot_max_amount` 需后台审核)。**客户端用此字段做大奖样式,勿仅依赖 `jackpot.hit`** - **`data.is_win`**:`bool`,固定为 `true`(便于与 `user.streak` 的 `extra.is_win` 对齐) + - **`data.payout_pending_review`**:`bool`,`true` 表示已中奖但派彩待后台大奖审核,尚未入账(仍应展示中奖 UI) - **合并赔率字段**(与 §7.1.2A 一致):`current_streak`、`streak_level`、`odds_factor`、`is_jackpot` - `data.server_time`:Unix 秒 - **服务端去重**:Redis Key `dfw:v1:ws:betwin:{period_id}:{user_id}`(TTL 86400s),**每期每用户至多推送一次**;与 `user.streak` / `wallet.changed` 的整期去重键 `dfw:v1:settle:notify:{period_id}` **分离**,避免后者先占位导致 `bet.win` 被吞。