$payload * @return array */ public function preview(array $payload): array { $draw = Draw::query()->where('draw_no', (string) $payload['draw_id'])->first(); if ($draw === null) { throw new TicketOperationException('draw_not_found', ErrorCode::BetInvalidDraw->value); } if ($draw->status !== DrawStatus::Open->value) { throw new TicketOperationException('draw_closed', ErrorCode::DrawClosed->value); } $currencyCode = strtoupper((string) $payload['currency_code']); $lines = []; $totalBet = 0; $totalRebate = 0; $totalActualDeduct = 0; $totalEstimatedPayout = 0; $warningRows = []; $closedPlayCleanupRows = []; foreach ((array) $payload['lines'] as $index => $line) { try { $resolved = $this->catalogResolver->resolve((string) $line['play_code'], $currencyCode); } catch (TicketOperationException $e) { if ($e->lotteryCode === ErrorCode::PlayModeClosed->value) { $closedPlayCleanupRows[] = [ 'client_line_no' => $index + 1, 'play_code' => (string) ($line['play_code'] ?? ''), ]; continue; } throw $e; } $evaluated = $this->ruleEngine->evaluateLine( (array) $line, $resolved['play_type'], $resolved['play_config'], $resolved['odds_items'], ); $locks = array_map(fn (array $combo): array => [ 'number_4d' => $combo['number_4d'], 'amount' => $combo['estimated_payout'], ], $evaluated['combinations']); $riskPreview = $this->riskPoolService->preview((int) $draw->id, $locks); foreach ($riskPreview as $riskRow) { if ($riskRow['warning']) { $warningRows[] = [ 'number_4d' => $riskRow['number_4d'], 'message' => '该号码赔付池已使用 80% 以上,可能即将售罄', ]; } } $rebateAmount = (int) $evaluated['total_bet_amount'] - (int) $evaluated['actual_deduct_amount']; $totalBet += (int) $evaluated['total_bet_amount']; $totalRebate += $rebateAmount; $totalActualDeduct += (int) $evaluated['actual_deduct_amount']; $totalEstimatedPayout += (int) $evaluated['estimated_max_payout']; $lines[] = [ 'client_line_no' => $index + 1, 'number' => $evaluated['original_number'], 'play_code' => $evaluated['play_code'], 'normalized_number' => $evaluated['normalized_number'], 'combination_count' => $evaluated['combination_count'], 'total_bet_amount' => $evaluated['total_bet_amount'], 'rebate_rate' => $evaluated['rebate_rate_snapshot'], 'rebate_amount' => $rebateAmount, 'actual_deduct_amount' => $evaluated['actual_deduct_amount'], 'estimated_max_payout' => $evaluated['estimated_max_payout'], 'risk_status' => 'ok', 'warnings' => [], 'rule_snapshot_json' => $evaluated['rule_snapshot_json'], ]; } if ($closedPlayCleanupRows !== []) { throw new TicketOperationException( 'play_closed_need_cleanup', ErrorCode::PlayModeClosed->value, 400, [ 'cleanup_hint' => '玩法已关闭,相关注项已清理', 'cleanup_lines' => $closedPlayCleanupRows, ], ); } return [ 'draw' => [ 'draw_id' => $draw->draw_no, 'status' => $draw->status, ], 'config_versions' => $this->catalogResolver->currentActiveVersionStamp(), 'summary' => [ 'total_bet_amount' => $totalBet, 'total_rebate_amount' => $totalRebate, 'total_actual_deduct' => $totalActualDeduct, 'total_estimated_payout' => $totalEstimatedPayout, ], 'lines' => $lines, 'warnings' => $warningRows, ]; } }