feat(admin): 统一后台 API 资源鉴权并完善投注风控快照与回补

This commit is contained in:
2026-05-19 09:11:50 +08:00
parent 6ef41cee76
commit 4cf561cd57
26 changed files with 1079 additions and 36 deletions

View File

@@ -6,6 +6,7 @@ use App\Models\Draw;
use App\Models\Player;
use App\Lottery\ErrorCode;
use App\Models\TicketItem;
use App\Models\WalletTxn;
use App\Lottery\DrawStatus;
use App\Models\TicketOrder;
use App\Models\PlayerWallet;
@@ -31,6 +32,22 @@ final class TicketPlacementService
public function place(Player $player, array $payload): array
{
$currencyCode = strtoupper((string) $payload['currency_code']);
$clientTraceId = isset($payload['client_trace_id']) && $payload['client_trace_id'] !== ''
? (string) $payload['client_trace_id']
: null;
if ($clientTraceId !== null) {
$existing = TicketOrder::query()
->where('player_id', $player->id)
->where('client_trace_id', $clientTraceId)
->whereIn('status', ['placed', 'partial_failed'])
->first();
if ($existing !== null) {
return $this->responseForOrder($existing, null);
}
}
$expectedVersions = $payload['expected_config_versions'] ?? null;
if (is_array($expectedVersions)) {
$expectedVersions = [
@@ -59,7 +76,7 @@ final class TicketPlacementService
throw new TicketOperationException('draw_closed', ErrorCode::DrawClosed->value);
}
$this->catalogResolver->lockActiveConfigVersionsForPlacement($expectedVersions);
$configVersions = $this->catalogResolver->lockActiveConfigVersionsForPlacement($expectedVersions);
$evaluatedLines = [];
$totalBet = 0;
@@ -137,6 +154,9 @@ final class TicketPlacementService
'status' => 'pending',
'submit_source' => 'h5',
'client_trace_id' => $payload['client_trace_id'] ?? null,
'play_config_version_no' => $configVersions['play_config_version_no'],
'odds_version_no' => $configVersions['odds_version_no'],
'risk_cap_version_no' => $configVersions['risk_cap_version_no'],
]);
$successfulItems = [];
@@ -297,6 +317,7 @@ final class TicketPlacementService
}
$order->forceFill(['status' => 'refunded'])->save();
$this->ticketWalletService->reverseBetDeduct($order);
});
throw $e;
@@ -304,6 +325,24 @@ final class TicketPlacementService
$order = TicketOrder::query()->whereKey($order->id)->firstOrFail();
return $this->responseForOrder($order, $balanceAfter);
}
private function responseForOrder(TicketOrder $order, ?int $balanceAfter): array
{
$order = TicketOrder::query()->whereKey($order->id)->firstOrFail();
$draw = Draw::query()->whereKey((int) $order->draw_id)->firstOrFail();
$successCount = TicketItem::query()->where('order_id', $order->id)->where('status', 'success')->count();
$failureCount = TicketItem::query()->where('order_id', $order->id)->where('status', 'failed')->count();
if ($balanceAfter === null) {
$walletTxn = WalletTxn::query()
->where('biz_type', 'bet_deduct')
->where('biz_no', $order->order_no)
->latest('id')
->first();
$balanceAfter = $walletTxn === null ? null : (int) $walletTxn->balance_after;
}
return [
'order_no' => $order->order_no,
'draw' => [
@@ -315,8 +354,8 @@ final class TicketPlacementService
'total_rebate_amount' => (int) $order->total_rebate_amount,
'total_actual_deduct' => (int) $order->total_actual_deduct,
'total_estimated_payout' => (int) $order->total_estimated_payout,
'success_count' => TicketItem::query()->where('order_id', $order->id)->where('status', 'success')->count(),
'failure_count' => TicketItem::query()->where('order_id', $order->id)->where('status', 'failed')->count(),
'success_count' => $successCount,
'failure_count' => $failureCount,
],
'balance_after' => $balanceAfter,
'items' => TicketItem::query()