Files
lotteryLaravel/app/Http/Controllers/Api/V1/Wallet/WalletLogsController.php

191 lines
5.9 KiB
PHP
Raw 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\Http\Controllers\Api\V1\Wallet;
use App\Models\WalletTxn;
use Illuminate\Support\Str;
use App\Support\ApiResponse;
use Illuminate\Http\Request;
use App\Models\TransferOrder;
use App\Support\PaginationTrait;
use Illuminate\Http\JsonResponse;
use App\Support\CurrencyFormatter;
use App\Http\Controllers\Controller;
/**
* PRD §10.1.1`GET /api/v1/wallet/logs` — 钱包流水。
*
* Query`page`、`size`(每页条数,默认 20、`type`逗号分隔transfer_in,transfer_out,bet,prize,refund,reversal
*/
final class WalletLogsController extends Controller
{
use PaginationTrait;
/** PRD 对外类型 → 本地 biz_type */
private const TYPE_TO_BIZ = [
'transfer_in' => ['transfer_in'],
'transfer_out' => ['transfer_out'],
'refund' => ['transfer_out_refund'],
'reversal' => ['reversal'],
'bet' => ['bet_deduct', 'bet'],
'prize' => ['settle_payout', 'prize'],
];
public function __invoke(Request $request): JsonResponse
{
$player = $request->lotteryPlayer();
abort_if($player === null, 500, 'lottery_player missing');
$perPage = $this->perPage($request, 'size', 20, 100);
$page = $this->page($request);
$pendingPayload = $this->pendingReconcilePayload((int) $player->id);
$bizFilter = $this->resolveBizTypeFilter((string) $request->query('type', ''));
if (is_array($bizFilter) && $bizFilter === []) {
return ApiResponse::success([
'items' => [],
'total' => 0,
'page' => $page,
'per_page' => $perPage,
'pending_reconcile' => $pendingPayload,
]);
}
$query = WalletTxn::query()
->where('player_id', $player->id)
->with('wallet')
->orderByDesc('id');
if ($bizFilter !== null) {
$query->whereIn('biz_type', $bizFilter);
}
$paginator = $query->paginate($perPage, ['*'], 'page', $page);
$items = $paginator->getCollection()->map(fn (WalletTxn $txn) => $this->formatTxn($txn));
return ApiResponse::success([
'items' => $items,
'total' => $paginator->total(),
'page' => $paginator->currentPage(),
'per_page' => $paginator->perPage(),
'pending_reconcile' => $pendingPayload,
]);
}
/**
* @return list<array<string, mixed>>
*/
private function pendingReconcilePayload(int $playerId): array
{
return TransferOrder::query()
->where('player_id', $playerId)
->where('status', 'pending_reconcile')
->orderByDesc('id')
->limit(50)
->get()
->map(fn (TransferOrder $o) => $this->formatPendingOrder($o))
->all();
}
/**
* @return list<string>|null null 表示不过滤;空列表表示过滤后无合法 type结果应为空
*/
private function resolveBizTypeFilter(string $raw): ?array
{
$raw = trim($raw);
if ($raw === '') {
return null;
}
$parts = array_filter(array_map('trim', explode(',', $raw)));
if ($parts === []) {
return null;
}
$biz = [];
foreach ($parts as $p) {
$key = Str::lower($p);
if (! isset(self::TYPE_TO_BIZ[$key])) {
continue;
}
foreach (self::TYPE_TO_BIZ[$key] as $b) {
$biz[] = $b;
}
}
return array_values(array_unique($biz));
}
/**
* @return array<string, mixed>
*/
private function formatTxn(WalletTxn $txn): array
{
$currency = $txn->wallet?->currency_code ?? '';
$amount = (int) $txn->amount;
$balanceAfter = (int) $txn->balance_after;
return [
'log_id' => $txn->txn_no,
'type' => $this->bizToPublicType((string) $txn->biz_type),
'biz_type' => $txn->biz_type,
'amount' => $this->signedAmount($txn),
'amount_formatted' => CurrencyFormatter::fromMinor($amount),
'amount_abs' => $amount,
'amount_abs_formatted' => CurrencyFormatter::fromMinor($amount),
'direction' => (int) $txn->direction === 1 ? 'in' : 'out',
'currency_code' => $currency,
'balance_after' => $balanceAfter,
'balance_after_formatted' => CurrencyFormatter::fromMinor($balanceAfter),
'ref_id' => $txn->biz_no,
'idempotent_key' => $txn->idempotent_key,
'external_ref_no' => $txn->external_ref_no,
'status' => $txn->status,
'remark' => $txn->remark,
'created_at' => $txn->created_at?->toIso8601String(),
];
}
private function bizToPublicType(string $biz): string
{
return match ($biz) {
'transfer_out_refund' => 'refund',
'bet_deduct' => 'bet',
'settle_payout' => 'prize',
'reversal' => 'reversal',
default => $biz,
};
}
private function signedAmount(WalletTxn $txn): int
{
$a = (int) $txn->amount;
return (int) $txn->direction === 1 ? $a : -$a;
}
/**
* @return array<string, mixed>
*/
private function formatPendingOrder(TransferOrder $order): array
{
$amount = (int) $order->amount;
return [
'transfer_no' => $order->transfer_no,
'direction' => $order->direction,
'type' => $order->direction === 'in' ? 'transfer_in' : 'transfer_out',
'currency_code' => $order->currency_code,
'amount' => $amount,
'amount_formatted' => CurrencyFormatter::fromMinor($amount),
'status' => $order->status,
'fail_reason' => $order->fail_reason,
'idempotent_key' => $order->idempotent_key,
'created_at' => $order->created_at?->toIso8601String(),
];
}
}