feat(core): harden sessions settlement and credit activity
This commit is contained in:
@@ -3,19 +3,20 @@
|
||||
namespace App\Services\Wallet;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use App\Models\AdminUser;
|
||||
use App\Models\Player;
|
||||
use App\Models\AdminUser;
|
||||
use App\Models\WalletTxn;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Support\AdminDataScope;
|
||||
use App\Support\CurrencyFormatter;
|
||||
use App\Support\LimitedQuery;
|
||||
use App\Support\AdminDataScope;
|
||||
use App\Support\CreditAmountScale;
|
||||
use App\Support\CurrencyFormatter;
|
||||
use App\Support\PlayerFundingMode;
|
||||
use App\Services\AgentSettlement\CreditLedgerBetFlowPresenter;
|
||||
use App\Services\AgentSettlement\SettlementPartyEnrichment;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Services\Player\PlayerCreditService;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use App\Services\AgentSettlement\SettlementPartyEnrichment;
|
||||
use App\Services\AgentSettlement\CreditLedgerBetFlowPresenter;
|
||||
|
||||
/**
|
||||
* 玩家流水:钱包玩家读 {@see wallet_txns},信用盘玩家读 {@see credit_ledger}。
|
||||
@@ -48,6 +49,7 @@ final class PlayerLedgerLogsService
|
||||
|
||||
public function __construct(
|
||||
private readonly PlayerCreditService $playerCreditService,
|
||||
private readonly PlayerCreditActivityService $creditActivityService,
|
||||
private readonly CreditLedgerBetFlowPresenter $betFlowPresenter,
|
||||
private readonly SettlementPartyEnrichment $partyEnrichment,
|
||||
) {}
|
||||
@@ -430,6 +432,127 @@ final class PlayerLedgerLogsService
|
||||
int $page,
|
||||
int $perPage,
|
||||
string $typeFilterRaw,
|
||||
): array {
|
||||
$activities = $this->creditActivityService->build($player);
|
||||
if ($activities !== []) {
|
||||
return $this->paginateCreditActivities($player, $activities, $page, $perPage, $typeFilterRaw);
|
||||
}
|
||||
|
||||
return $this->paginateLegacyCreditLedger($player, $page, $perPage, $typeFilterRaw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string, mixed>> $activities
|
||||
* @return array{items: list<array<string, mixed>>, total: int, page: int, per_page: int}
|
||||
*/
|
||||
private function paginateCreditActivities(
|
||||
Player $player,
|
||||
array $activities,
|
||||
int $page,
|
||||
int $perPage,
|
||||
string $typeFilterRaw,
|
||||
): array {
|
||||
$currency = (string) $player->default_currency;
|
||||
$creditLimitMinor = $this->creditLimitMinor($player, $currency);
|
||||
$runningMinor = $this->clampCreditAvailableMinor(
|
||||
$this->playerCreditService->availableCreditMinor($player, $currency),
|
||||
$creditLimitMinor,
|
||||
);
|
||||
|
||||
// 先按完整活动时间线回推每笔的变更后额度,再做筛选和分页。
|
||||
// 否则筛选掉的较新活动会让历史记录的 balance_after 偏离真实值。
|
||||
foreach ($activities as &$activity) {
|
||||
$activity['balance_after'] = $runningMinor;
|
||||
$activity['balance_after_formatted'] = CurrencyFormatter::fromMinor($runningMinor);
|
||||
$activity['funding_mode'] = (string) ($player->funding_mode ?? PlayerFundingMode::CREDIT);
|
||||
$activity['auth_source'] = $player->auth_source;
|
||||
$runningMinor = $this->clampCreditAvailableMinor(
|
||||
$runningMinor - (int) ($activity['_available_delta'] ?? 0),
|
||||
$creditLimitMinor,
|
||||
);
|
||||
unset($activity['_available_delta']);
|
||||
}
|
||||
unset($activity);
|
||||
|
||||
$activities = array_values(array_filter(
|
||||
$activities,
|
||||
fn (array $activity): bool => $this->creditActivityMatchesFilter($activity, $typeFilterRaw),
|
||||
));
|
||||
$offset = max(0, ($page - 1) * $perPage);
|
||||
|
||||
$items = array_slice($activities, $offset, $perPage);
|
||||
|
||||
return [
|
||||
'items' => $items,
|
||||
'total' => count($activities),
|
||||
'page' => $page,
|
||||
'per_page' => $perPage,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $activity
|
||||
*/
|
||||
private function creditActivityMatchesFilter(array $activity, string $typeFilterRaw): bool
|
||||
{
|
||||
$raw = trim($typeFilterRaw);
|
||||
if ($raw === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$parts = array_values(array_filter(array_map(
|
||||
static fn (string $part): string => Str::lower(trim($part)),
|
||||
explode(',', $raw),
|
||||
)));
|
||||
if ($parts === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$kind = (string) ($activity['activity_kind'] ?? '');
|
||||
$status = (string) ($activity['activity_status'] ?? '');
|
||||
$netAmount = (int) ($activity['net_amount'] ?? 0);
|
||||
$rebateAmount = (int) ($activity['rebate_amount'] ?? 0);
|
||||
|
||||
foreach ($parts as $part) {
|
||||
if ($part === 'bet' && $kind === 'bet') {
|
||||
return true;
|
||||
}
|
||||
if ($part === 'game_settlement' && $kind === 'draw_result') {
|
||||
return true;
|
||||
}
|
||||
if ($part === 'bill_settlement' && $kind === 'period_settlement') {
|
||||
return true;
|
||||
}
|
||||
if ($part === 'rebate' && $kind === 'draw_result' && $rebateAmount > 0) {
|
||||
return true;
|
||||
}
|
||||
if ($part === 'reversal' && ($kind === 'refund' || $status === 'reversed')) {
|
||||
return true;
|
||||
}
|
||||
if ($part === 'refund' && $kind === 'refund') {
|
||||
return true;
|
||||
}
|
||||
if ($part === 'win_credit' && $kind === 'draw_result' && $netAmount > 0) {
|
||||
return true;
|
||||
}
|
||||
if ($part === 'credit_release' && in_array($kind, ['draw_result', 'refund', 'period_settlement'], true)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容没有订单领域记录的旧数据与孤立测试数据;真实玩家活动优先走聚合视图。
|
||||
*
|
||||
* @return array{items: list<array<string, mixed>>, total: int, page: int, per_page: int}
|
||||
*/
|
||||
private function paginateLegacyCreditLedger(
|
||||
Player $player,
|
||||
int $page,
|
||||
int $perPage,
|
||||
string $typeFilterRaw,
|
||||
): array {
|
||||
$reasonFilter = $this->resolveCreditReasonFilter($typeFilterRaw);
|
||||
$includeRebates = $this->creditFilterIncludesRebates($typeFilterRaw);
|
||||
@@ -732,7 +855,7 @@ final class PlayerLedgerLogsService
|
||||
return 0;
|
||||
}
|
||||
|
||||
return \App\Support\CreditAmountScale::majorToMinor((int) $credit->credit_limit, $currency);
|
||||
return CreditAmountScale::majorToMinor((int) $credit->credit_limit, $currency);
|
||||
}
|
||||
|
||||
private function clampCreditAvailableMinor(int $amountMinor, int $creditLimitMinor): int
|
||||
|
||||
Reference in New Issue
Block a user