在 TicketItemShowController 与 TicketItemsIndexController 的响应中新增订单状态与失败原因字段。 更新 WalletLogsController:待对账列表支持按币种筛选。 在 TicketPlacementService 中引入幂等性校验,支持处理已退款订单的重复请求。 优化钱包相关操作的错误码与错误提示信息,提升问题定位与用户理解。 增强测试用例,验证票据下单流程中的新幂等性行为。
145 lines
5.3 KiB
PHP
145 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api\V1\Ticket;
|
|
|
|
use Carbon\Carbon;
|
|
use App\Models\Player;
|
|
use App\Models\TicketItem;
|
|
use App\Support\ApiResponse;
|
|
use Illuminate\Http\Request;
|
|
use App\Support\PaginationTrait;
|
|
use Illuminate\Http\JsonResponse;
|
|
use App\Support\CurrencyFormatter;
|
|
use App\Http\Controllers\Controller;
|
|
|
|
/**
|
|
* `GET /api/v1/ticket/items` — 我的注单(注项列表,支持 `draw_no` 筛选)。
|
|
*/
|
|
final class TicketItemsIndexController extends Controller
|
|
{
|
|
use PaginationTrait;
|
|
|
|
public function __invoke(Request $request): JsonResponse
|
|
{
|
|
/** @var Player $player */
|
|
$player = $request->attributes->get('lottery_player');
|
|
|
|
$perPage = $this->perPage($request, 'per_page', 10, 50);
|
|
$page = $this->page($request);
|
|
$drawNo = $request->query('draw_no');
|
|
$statusInput = $request->query('status', []);
|
|
if (is_string($statusInput)) {
|
|
$statusInput = [$statusInput];
|
|
}
|
|
$statusValues = is_array($statusInput) ? array_values(array_filter(array_map(
|
|
fn ($status) => is_string($status) ? trim($status) : '',
|
|
$statusInput,
|
|
))) : [];
|
|
$number = trim((string) $request->query('number', ''));
|
|
$startDate = $this->normalizeDate((string) $request->query('start_date', ''));
|
|
$endDate = $this->normalizeDate((string) $request->query('end_date', ''));
|
|
|
|
$query = TicketItem::query()
|
|
->where('ticket_items.player_id', $player->id)
|
|
->with([
|
|
'draw:id,draw_no,business_date',
|
|
'order:id,order_no,currency_code,status,created_at',
|
|
])
|
|
->orderByDesc('ticket_items.id');
|
|
|
|
if (is_string($drawNo) && $drawNo !== '') {
|
|
$drawNo = trim($drawNo);
|
|
$query->whereHas('draw', fn ($q) => $q->where('draw_no', $drawNo));
|
|
}
|
|
|
|
if ($statusValues !== []) {
|
|
$query->whereIn('ticket_items.status', $statusValues);
|
|
}
|
|
|
|
if ($number !== '') {
|
|
$query->where(function ($q) use ($number): void {
|
|
$q->where('ticket_items.original_number', 'like', '%'.$number.'%')
|
|
->orWhere('ticket_items.normalized_number', 'like', '%'.$number.'%')
|
|
->orWhere('ticket_items.ticket_no', 'like', '%'.$number.'%');
|
|
});
|
|
}
|
|
|
|
if ($startDate !== null) {
|
|
$fromUtc = $this->scheduleDateStartUtc($startDate);
|
|
$query->whereHas('order', fn ($q) => $q->where('created_at', '>=', $fromUtc));
|
|
}
|
|
|
|
if ($endDate !== null) {
|
|
$toUtc = $this->scheduleDateEndUtc($endDate);
|
|
$query->whereHas('order', fn ($q) => $q->where('created_at', '<=', $toUtc));
|
|
}
|
|
|
|
$paginator = $query->paginate(perPage: $perPage, page: $page);
|
|
|
|
$items = collect($paginator->items())->map(function (TicketItem $row): array {
|
|
$totalBet = (int) $row->total_bet_amount;
|
|
$actualDeduct = (int) $row->actual_deduct_amount;
|
|
$winAmount = (int) $row->win_amount;
|
|
$jackpotWin = (int) $row->jackpot_win_amount;
|
|
|
|
return [
|
|
'ticket_no' => $row->ticket_no,
|
|
'order_no' => $row->order?->order_no,
|
|
'order_status' => $row->order?->status,
|
|
'draw_no' => $row->draw?->draw_no,
|
|
'currency_code' => $row->order?->currency_code,
|
|
'play_code' => $row->play_code,
|
|
'original_number' => $row->original_number,
|
|
'total_bet_amount' => $totalBet,
|
|
'total_bet_amount_formatted' => CurrencyFormatter::fromMinor($totalBet),
|
|
'actual_deduct_amount' => $actualDeduct,
|
|
'actual_deduct_amount_formatted' => CurrencyFormatter::fromMinor($actualDeduct),
|
|
'status' => $row->status,
|
|
'win_amount' => $winAmount,
|
|
'win_amount_formatted' => CurrencyFormatter::fromMinor($winAmount),
|
|
'jackpot_win_amount' => $jackpotWin,
|
|
'jackpot_win_amount_formatted' => CurrencyFormatter::fromMinor($jackpotWin),
|
|
'placed_at' => $row->order?->created_at?->toIso8601String(),
|
|
'updated_at' => $row->updated_at?->toIso8601String(),
|
|
];
|
|
})->values()->all();
|
|
|
|
return ApiResponse::success([
|
|
'items' => $items,
|
|
'total' => $paginator->total(),
|
|
'page' => $paginator->currentPage(),
|
|
'per_page' => $paginator->perPage(),
|
|
'last_page' => $paginator->lastPage(),
|
|
]);
|
|
}
|
|
|
|
private function normalizeDate(string $value): ?string
|
|
{
|
|
$value = trim($value);
|
|
if ($value === '' || ! preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
|
|
return null;
|
|
}
|
|
|
|
return $value;
|
|
}
|
|
|
|
private function scheduleTimezone(): string
|
|
{
|
|
return (string) config('lottery.draw.timezone', 'UTC');
|
|
}
|
|
|
|
private function scheduleDateStartUtc(string $ymd): Carbon
|
|
{
|
|
return Carbon::createFromFormat('Y-m-d', $ymd, $this->scheduleTimezone())
|
|
->startOfDay()
|
|
->utc();
|
|
}
|
|
|
|
private function scheduleDateEndUtc(string $ymd): Carbon
|
|
{
|
|
return Carbon::createFromFormat('Y-m-d', $ymd, $this->scheduleTimezone())
|
|
->endOfDay()
|
|
->utc();
|
|
}
|
|
}
|