feat: 增强奖池与钱包服务的多币种支持能力

更新 JackpotManualBurstService:在解析头奖中奖者时支持币种代码处理。
重构 SettlementBatchWorkflowService:按币种聚合玩家派奖金额,确保各币种结算准确入账。
修改 SettlementOrchestrator:按币种分别处理奖池爆奖流程,提升派奖准确性。
优化 TicketWalletService:在派奖幂等键中加入币种信息,避免多币种场景下的重复处理问题。
新增测试用例,验证多币种派奖场景及待处理交易的正确处理逻辑。
This commit is contained in:
2026-05-28 16:50:24 +08:00
parent 8ccf39dff5
commit 0323d92381
8 changed files with 857 additions and 73 deletions

View File

@@ -121,8 +121,7 @@ final class SettlementBatchWorkflowService
}
$details = $locked->details()->with(['ticketItem.order'])->get();
$playerTotals = [];
$currencyByPlayer = [];
$playerCurrencyTotals = [];
foreach ($details as $detail) {
$item = $detail->ticketItem;
@@ -132,20 +131,34 @@ final class SettlementBatchWorkflowService
$finalCredit = (int) $detail->win_amount + (int) $detail->jackpot_allocation_amount;
if ($finalCredit > 0) {
$pid = (int) $item->player_id;
$playerTotals[$pid] = ($playerTotals[$pid] ?? 0) + $finalCredit;
$currencyByPlayer[$pid] = strtoupper((string) ($item->order?->currency_code ?? 'NPR'));
$currency = strtoupper((string) ($item->order?->currency_code ?? 'NPR'));
$aggregateKey = $pid.':'.$currency;
if (! isset($playerCurrencyTotals[$aggregateKey])) {
$playerCurrencyTotals[$aggregateKey] = [
'player_id' => $pid,
'currency_code' => $currency,
'amount' => 0,
];
}
$playerCurrencyTotals[$aggregateKey]['amount'] += $finalCredit;
$item->forceFill(['status' => 'settled_win', 'settled_at' => now()])->save();
} elseif ($item->status !== 'settled_lose') {
$item->forceFill(['status' => 'settled_lose', 'settled_at' => now()])->save();
}
}
foreach ($playerTotals as $playerId => $amount) {
foreach ($playerCurrencyTotals as $entry) {
$amount = (int) $entry['amount'];
if ($amount <= 0) {
continue;
}
$player = Player::query()->whereKey($playerId)->firstOrFail();
$this->wallet->creditSettlementPayout($player, $currencyByPlayer[$playerId] ?? 'NPR', $amount, (int) $locked->id);
$player = Player::query()->whereKey((int) $entry['player_id'])->firstOrFail();
$this->wallet->creditSettlementPayout(
$player,
(string) $entry['currency_code'],
$amount,
(int) $locked->id
);
}
$orderIds = TicketItem::query()
@@ -184,23 +197,35 @@ final class SettlementBatchWorkflowService
return;
}
$orderId = TicketItem::query()->where('draw_id', $batch->draw_id)->value('order_id');
$currencyCode = strtoupper((string) (TicketOrder::query()
->whereKey($orderId)
->value('currency_code') ?? 'NPR'));
$details = $batch->details()->with(['ticketItem.order'])->get();
$restoreByCurrency = [];
foreach ($details as $detail) {
$amount = (int) $detail->jackpot_allocation_amount;
if ($amount <= 0) {
continue;
}
$currency = strtoupper((string) ($detail->ticketItem?->order?->currency_code ?? 'NPR'));
$restoreByCurrency[$currency] = ($restoreByCurrency[$currency] ?? 0) + $amount;
}
$pool = JackpotPool::query()
->where('currency_code', $currencyCode)
->where('status', 1)
->lockForUpdate()
->first();
if ($pool === null) {
if ($restoreByCurrency === []) {
return;
}
$pool->forceFill([
'current_amount' => (int) $pool->current_amount + $restoreAmount,
])->save();
foreach ($restoreByCurrency as $currency => $amount) {
$pool = JackpotPool::query()
->where('currency_code', $currency)
->where('status', 1)
->lockForUpdate()
->first();
if ($pool === null) {
continue;
}
$pool->forceFill([
'current_amount' => (int) $pool->current_amount + (int) $amount,
])->save();
}
}
}