player; if ($player === null) { return false; } return PlayerFundingMode::usesCredit($player) && (int) ($player->agent_node_id ?? 0) > 0; } public function recordForTicketItem(TicketItem $item, int $netWin, string $terminalStatus): void { if (! $this->shouldRecord($item)) { return; } $player = $item->player; if ($player === null) { return; } $gameType = trim((string) ($item->play_code ?? '')) ?: '*'; $snapshot = $this->snapshotBuilder->buildForPlayer($player, $gameType); $baseRebateRate = $this->baseRebateRateForTicketItem($item); $basicRebateRate = $this->normalizeRate($baseRebateRate + (float) $snapshot['rebate_rate']); $shareSnapshot = [ ...$snapshot, 'base_rebate_rate' => $baseRebateRate, 'basic_rebate_rate' => $basicRebateRate, ]; $gameWinLoss = $this->platformWinLoss($item, $netWin, $terminalStatus); $validBet = (int) $item->total_bet_amount; $basicRebate = (int) round($validBet * $basicRebateRate); $extraRebate = (int) round($validBet * $snapshot['extra_rebate_rate']); $extraByCode = []; if ($extraRebate > 0 && $snapshot['chain_codes'] !== []) { $leaf = $snapshot['chain_codes'][0]; $extraByCode[$leaf] = $extraRebate; } $result = $this->calculator->calculate( sharedNetWinLoss: 0, totalSharesByCode: $snapshot['total_shares'], extraRebateByCode: $extraByCode, gameWinLoss: $gameWinLoss, basicRebate: $basicRebate, chainFromPlayer: $snapshot['chain_codes'], ); $settledAt = now(); DB::transaction(function () use ($item, $player, $snapshot, $shareSnapshot, $basicRebateRate, $gameWinLoss, $basicRebate, $result, $settledAt, $validBet, $extraRebate, $gameType): void { $item->forceFill([ 'agent_node_id' => $snapshot['agent_node_id'], 'share_snapshot' => $shareSnapshot, 'agent_rebate_rate_snapshot' => $basicRebateRate, 'agent_settled_at' => $settledAt, ])->save(); DB::table('share_ledger')->insert([ 'ticket_item_id' => $item->id, 'player_id' => $player->id, 'agent_node_id' => $snapshot['agent_node_id'], 'agent_path' => json_encode($snapshot['agent_path']), 'share_snapshot' => json_encode($shareSnapshot), 'game_win_loss' => (int) round($gameWinLoss), 'basic_rebate' => $basicRebate, 'shared_net_win_loss' => (int) round($result->sharedNetWinLoss), 'allocations_json' => json_encode($result->finalProfits), 'settled_at' => $settledAt, 'created_at' => $settledAt, 'updated_at' => $settledAt, ]); if ($basicRebate > 0) { DB::table('rebate_records')->insert([ 'player_id' => $player->id, 'ticket_item_id' => $item->id, 'game_type' => $gameType, 'valid_bet_amount' => $validBet, 'rebate_rate' => $basicRebateRate, 'rebate_amount' => $basicRebate, 'rebate_type' => 'basic', 'owner_agent_id' => $snapshot['agent_node_id'], 'status' => 'accrued', 'created_at' => $settledAt, 'updated_at' => $settledAt, ]); } if ($extraRebate > 0) { DB::table('rebate_records')->insert([ 'player_id' => $player->id, 'ticket_item_id' => $item->id, 'game_type' => $gameType, 'valid_bet_amount' => $validBet, 'rebate_rate' => $snapshot['extra_rebate_rate'], 'rebate_amount' => $extraRebate, 'rebate_type' => 'extra', 'owner_agent_id' => $snapshot['agent_node_id'], 'status' => 'accrued', 'created_at' => $settledAt, 'updated_at' => $settledAt, ]); } $holdAmount = (int) $item->actual_deduct_amount; if ($holdAmount > 0) { $this->playerCreditService->releaseBetHold($player, $holdAmount, $item->id); } if ($gameWinLoss > 0) { $this->playerCreditService->applySettledLoss($player, (int) round($gameWinLoss), $item->id); } elseif ($gameWinLoss < 0) { $this->playerCreditService->applySettledWin($player, (int) round(abs($gameWinLoss)), $item->id); } }); } private function baseRebateRateForTicketItem(TicketItem $item): float { $ruleSnapshot = is_array($item->rule_snapshot_json) ? $item->rule_snapshot_json : []; $baseFromRule = isset($ruleSnapshot['base_rebate_rate']) ? (float) $ruleSnapshot['base_rebate_rate'] : null; if ($baseFromRule !== null && $baseFromRule > 0) { return $this->normalizeRate($baseFromRule); } $oddsSnapshot = is_array($item->odds_snapshot_json) ? $item->odds_snapshot_json : []; foreach ($oddsSnapshot as $row) { if (! is_array($row)) { continue; } if (! array_key_exists('rebate_rate', $row)) { continue; } return $this->normalizeRate((float) $row['rebate_rate']); } return 0.0; } private function normalizeRate(float $rate): float { return max(0.0, min(1.0, $rate)); } private function platformWinLoss(TicketItem $item, int $netWin, string $terminalStatus): float { if ($terminalStatus === 'settled_lose') { return (float) max(0, (int) $item->actual_deduct_amount); } if ($netWin > 0) { return -1 * (float) $netWin; } return 0.0; } }