Files
lotteryLaravel/app/Services/AgentSettlement/AgentGameSettlementRecorder.php
kang a44679665d feat: 增强代理和玩家管理功能
- 在多个控制器中更新权限检查逻辑,确保管理员能够更灵活地管理代理和玩家。
- 在 AdminPlayerStoreController 中引入对玩家创建能力的验证,确保只有具备相应权限的管理员能够创建玩家。
- 更新请求验证逻辑,新增 credit_limit、rebate_rate 和 extra_rebate_rate 字段,以支持更细粒度的玩家管理。
- 在 AgentNodeProfileController 中添加对父代理能力授予的验证,确保子代理的权限在父代理范围内。
- 引入 AgentProfileFieldRules 以简化代理资料更新请求的规则定义,提升代码复用性。
2026-06-04 18:00:50 +08:00

152 lines
5.5 KiB
PHP

<?php
namespace App\Services\AgentSettlement;
use App\Models\Player;
use App\Models\TicketItem;
use App\Services\Player\PlayerCreditService;
use App\Support\PlayerFundingMode;
use Illuminate\Support\Facades\DB;
/**
* 彩票注单游戏结算侧入账:快照、占成流水、回水计提、玩家已用额度。
*/
final class AgentGameSettlementRecorder
{
public function __construct(
private readonly BetSettlementSnapshotBuilder $snapshotBuilder,
private readonly ShareSettlementCalculator $calculator,
private readonly PlayerCreditService $playerCreditService,
) {}
public function shouldRecord(TicketItem $item): bool
{
$player = $item->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);
$gameWinLoss = $this->platformWinLoss($item, $netWin, $terminalStatus);
$validBet = (int) $item->total_bet_amount;
$basicRebate = (int) round($validBet * $snapshot['rebate_rate']);
$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, $gameWinLoss, $basicRebate, $result, $settledAt, $validBet, $extraRebate): void {
$item->forceFill([
'agent_node_id' => $snapshot['agent_node_id'],
'share_snapshot' => [
'total_shares' => $snapshot['total_shares'],
'actual_shares' => $snapshot['actual_shares'],
'chain_codes' => $snapshot['chain_codes'],
],
'agent_rebate_rate_snapshot' => $snapshot['rebate_rate'],
'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($snapshot),
'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' => $snapshot['rebate_rate'],
'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);
}
});
}
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;
}
}