map(fn ($g) => array_merge($g, [ 'thumbnail_url' => "{$baseUrl}/assets/games/{$g['slug']}.png", 'launch_path' => "/{$g['slug']}", ])); return response()->json(['games' => $games]); } /** * POST /operator/launch * * Creates a new operator game session and returns the launch URL. */ public function launch(Request $request) { /** @var OperatorCasino $casino */ $casino = $request->attributes->get('operator_casino'); $data = $request->validate([ 'player_id' => ['required', 'string', 'max:255'], 'balance' => ['required', 'numeric', 'min:0'], 'currency' => ['required', 'string', 'size:3'], 'game' => ['required', 'string', 'in:' . implode(',', self::VALID_GAMES)], // license_key is consumed by middleware; allow it in the body without failing validation 'license_key' => ['sometimes', 'string'], ]); // Generate server seed for provably-fair and store it encrypted $serverSeed = bin2hex(random_bytes(32)); $serverSeedHash = hash('sha256', $serverSeed); $token = (string) Str::uuid(); $expiresAt = now()->addHours(4); OperatorSession::create([ 'session_token' => $token, 'operator_casino_id' => $casino->id, 'player_id' => $data['player_id'], 'game_slug' => $data['game'], 'currency' => strtoupper($data['currency']), 'start_balance' => $data['balance'], 'current_balance' => $data['balance'], 'server_seed' => encrypt($serverSeed), 'server_seed_hash' => $serverSeedHash, 'status' => 'active', 'expires_at' => $expiresAt, ]); $baseUrl = rtrim((string) config('games.game_base_url', config('app.url')), '/'); $launchUrl = "{$baseUrl}/{$data['game']}?session={$token}"; return response()->json([ 'session_token' => $token, 'launch_url' => $launchUrl, 'server_seed_hash' => $serverSeedHash, 'expires_at' => $expiresAt->toIso8601String(), ]); } /** * GET /operator/session/{token} * * Returns the current state of a session — including the final balance delta. * The casino should call this after the player's session ends. */ public function session(Request $request, string $token) { /** @var OperatorCasino $casino */ $casino = $request->attributes->get('operator_casino'); $session = OperatorSession::where('session_token', $token) ->where('operator_casino_id', $casino->id) ->firstOrFail(); $session->expireIfNeeded(); return response()->json([ 'session_token' => $session->session_token, 'player_id' => $session->player_id, 'game' => $session->game_slug, 'currency' => $session->currency, 'start_balance' => (float) $session->start_balance, 'current_balance' => (float) $session->current_balance, 'balance_delta' => round((float) $session->current_balance - (float) $session->start_balance, 4), 'status' => $session->status, 'expires_at' => $session->expires_at->toIso8601String(), ]); } }