Files
lotteryLaravel/app/Services/AgentSettlement/GameSettlementReversalService.php

131 lines
5.2 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services\AgentSettlement;
use App\Models\Player;
use App\Models\TicketItem;
use App\Support\PlayerFundingMode;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\QueryException;
use App\Services\Player\PlayerCreditService;
final class GameSettlementReversalService
{
public function __construct(
private readonly PlayerCreditService $playerCreditService,
) {}
public function reverseTicketItem(TicketItem $item, int $settlementVersion = 0): void
{
$ledger = DB::table('share_ledger as sl')
->where('sl.ticket_item_id', $item->id)
->whereNull('sl.reversal_of_id')
->whereNotExists(function ($query): void {
$query->selectRaw('1')
->from('share_ledger as reversal')
->whereColumn('reversal.reversal_of_id', 'sl.id');
})
->orderByDesc('sl.id')
->select('sl.*')
->first();
if ($ledger === null) {
return;
}
$settledAt = now();
DB::transaction(function () use ($item, $ledger, $settledAt, $settlementVersion): void {
// 幂等闸门share_ledger.reversal_of_id 上有 partial unique 索引,
// 同一原账不允许插入多条反转记录。重复调用时唯一约束冲突即视为已反转、跳过。
// 一次只让 reversal_of_id 出现一次写入确保所有后续副作用rebate、credit 冲正)
// 只在唯一反转成功路径上执行。
try {
DB::table('share_ledger')->insert([
'ticket_item_id' => $item->id,
'player_id' => $ledger->player_id,
'agent_node_id' => $ledger->agent_node_id,
'agent_path' => $ledger->agent_path,
'share_snapshot' => $ledger->share_snapshot,
'game_win_loss' => -1 * (int) $ledger->game_win_loss,
'basic_rebate' => -1 * (int) $ledger->basic_rebate,
'shared_net_win_loss' => -1 * (int) $ledger->shared_net_win_loss,
'allocations_json' => $ledger->allocations_json,
'reversal_of_id' => $ledger->id,
'settled_at' => $settledAt,
'created_at' => $settledAt,
'updated_at' => $settledAt,
]);
} catch (QueryException $e) {
if ($this->isUniqueViolation($e)) {
return;
}
throw $e;
}
$rebates = DB::table('rebate_records')
->where('ticket_item_id', $item->id)
->where('status', 'accrued')
->get();
foreach ($rebates as $rebate) {
DB::table('rebate_records')->insert([
'player_id' => $rebate->player_id,
'ticket_item_id' => $item->id,
'game_type' => $rebate->game_type,
'valid_bet_amount' => $rebate->valid_bet_amount,
'rebate_rate' => $rebate->rebate_rate,
'rebate_amount' => -1 * (int) $rebate->rebate_amount,
'rebate_type' => $rebate->rebate_type,
'owner_agent_id' => $rebate->owner_agent_id,
'status' => 'reversed',
'reversal_of_id' => $rebate->id,
'created_at' => $settledAt,
'updated_at' => $settledAt,
]);
DB::table('rebate_records')->where('id', $rebate->id)->update(['status' => 'reversed']);
}
$player = Player::query()->find((int) $ledger->player_id);
if ($player !== null && PlayerFundingMode::usesCredit($player)) {
$gameWinLoss = (int) $ledger->game_win_loss;
if ($gameWinLoss !== 0) {
$this->playerCreditService->reverseGameSettlement($player, $gameWinLoss, $item->id, $settlementVersion);
}
$this->playerCreditService->restoreBetHoldAfterSettlementReversal(
$player,
(int) $item->actual_deduct_amount,
(int) $item->id,
$settlementVersion,
);
}
});
}
/**
* 识别数据库唯一约束冲突PostgreSQL SQLSTATE 23505SQLite SQLSTATE 23000/error code 19
* 兜底扫描消息中 unique 字样,兼容驱动返回的 SQLSTATE 缺失场景。
*/
private function isUniqueViolation(QueryException $e): bool
{
$sqlState = (string) $e->getCode();
$errorInfo = $e->errorInfo ?? null;
$driverCode = is_array($errorInfo) ? (int) ($errorInfo[1] ?? 0) : 0;
if ($sqlState === '23505') {
return true;
}
if ($sqlState === '23000' || $sqlState === '19') {
return true;
}
if ($driverCode === 19 || $driverCode === 1062) {
return true;
}
$message = $e->getMessage();
if (stripos($message, 'unique') !== false || stripos($message, 'UNIQUE constraint') !== false) {
return true;
}
return false;
}
}