whereKey($batch->id)->lockForUpdate()->firstOrFail(); if ($locked->status !== SettlementBatchStatus::PendingReview->value) { throw new \RuntimeException('settlement_not_pending_review'); } $locked->forceFill([ 'status' => SettlementBatchStatus::Approved->value, 'review_status' => 'approved', 'reviewed_by' => $admin->id, 'reviewed_at' => now(), 'review_remark' => $remark, ])->save(); return $locked->refresh(); }); } public function reject(SettlementBatch $batch, AdminUser $admin, ?string $remark = null): SettlementBatch { return DB::transaction(function () use ($batch, $admin, $remark): SettlementBatch { /** @var SettlementBatch $locked */ $locked = SettlementBatch::query()->whereKey($batch->id)->lockForUpdate()->firstOrFail(); if ($locked->status !== SettlementBatchStatus::PendingReview->value) { throw new \RuntimeException('settlement_not_pending_review'); } TicketItem::query() ->whereIn('id', $locked->details()->pluck('ticket_item_id')) ->where('status', 'pending_payout') ->update(['status' => 'success', 'win_amount' => 0, 'jackpot_win_amount' => 0]); $locked->forceFill([ 'status' => SettlementBatchStatus::Rejected->value, 'review_status' => 'rejected', 'reviewed_by' => $admin->id, 'reviewed_at' => now(), 'review_remark' => $remark, ])->save(); return $locked->refresh(); }); } public function payout(SettlementBatch $batch): SettlementBatch { return DB::transaction(function () use ($batch): SettlementBatch { /** @var SettlementBatch $locked */ $locked = SettlementBatch::query()->whereKey($batch->id)->lockForUpdate()->firstOrFail(); if ($locked->status !== SettlementBatchStatus::Approved->value || $locked->review_status !== 'approved') { throw new \RuntimeException('settlement_not_approved'); } $details = $locked->details()->with(['ticketItem.order'])->get(); $playerTotals = []; $currencyByPlayer = []; foreach ($details as $detail) { $item = $detail->ticketItem; if ($item === null) { continue; } $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')); $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) { if ($amount <= 0) { continue; } $player = Player::query()->whereKey($playerId)->firstOrFail(); $this->wallet->creditSettlementPayout($player, $currencyByPlayer[$playerId] ?? 'NPR', $amount, (int) $locked->id); } $orderIds = TicketItem::query() ->whereIn('id', $locked->details()->pluck('ticket_item_id')) ->pluck('order_id') ->unique() ->all(); foreach ($orderIds as $orderId) { $pending = TicketItem::query() ->where('order_id', $orderId) ->whereNotIn('status', ['settled_win', 'settled_lose']) ->exists(); if (! $pending) { TicketOrder::query()->whereKey($orderId)->update(['status' => 'settled']); } } $locked->forceFill([ 'status' => SettlementBatchStatus::Paid->value, 'paid_at' => now(), ])->save(); Draw::query()->whereKey($locked->draw_id)->update([ 'status' => DrawStatus::Settled->value, 'settle_version' => (int) $locked->settle_version, ]); return $locked->refresh(); }); } }