where('ticket_item_id', $item->id)->whereNull('reversal_of_id')->first(); if ($ledger === null) { return; } $settledAt = now(); DB::transaction(function () use ($item, $ledger, $settledAt): 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); } } }); } /** * 识别数据库唯一约束冲突(PostgreSQL SQLSTATE 23505;SQLite 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; } }