recordTitleAttribute('paddle_transaction_id') ->columns([ TextColumn::make('tenant.name') ->label(__('Tenant')) ->searchable(), TextColumn::make('ip_address') ->label(__('IP')) ->copyable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('device_id') ->label(__('Device')) ->copyable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('user_agent') ->label(__('User agent')) ->toggleable(isToggledHiddenByDefault: true) ->wrap(), TextColumn::make('fraud_ip') ->label(__('IP reputation')) ->badge() ->formatStateUsing(fn ($state, $record) => self::formatReputation(data_get($record->metadata, 'fraud.ip'))) ->color(fn ($state, $record) => self::riskColor(data_get($record->metadata, 'fraud.ip.risk'))) ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('fraud_device') ->label(__('Device reputation')) ->badge() ->formatStateUsing(fn ($state, $record) => self::formatReputation(data_get($record->metadata, 'fraud.device'))) ->color(fn ($state, $record) => self::riskColor(data_get($record->metadata, 'fraud.device.risk'))) ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('user.name') ->label(__('User')) ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('amount_discounted') ->label(__('Discount')) ->formatStateUsing(fn ($state, $record) => sprintf('%s %s', number_format($record->amount_discounted, 2), $record->currency)) ->sortable(), TextColumn::make('status') ->label(__('Status')) ->badge() ->color(fn ($state) => match ($state) { 'success' => 'success', 'failed' => 'danger', default => 'warning', }), TextColumn::make('paddle_transaction_id') ->label(__('Transaction')) ->copyable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('redeemed_at') ->label(__('Redeemed at')) ->dateTime() ->sortable(), TextColumn::make('metadata') ->label(__('Metadata')) ->formatStateUsing(fn ($state) => $state ? json_encode($state, JSON_PRETTY_PRINT) : '—') ->toggleable(isToggledHiddenByDefault: true) ->copyable(), ]) ->filters([ SelectFilter::make('status') ->label(__('Status')) ->options([ 'pending' => __('Pending'), 'success' => __('Success'), 'failed' => __('Failed'), ]), ]) ->headerActions([ // read-only ]) ->recordActions([]) ->toolbarActions([]); } /** * @param array{risk?: string, recent_failed?: int, recent_total?: int}|null $snapshot */ private static function formatReputation(?array $snapshot): string { if (! $snapshot) { return '—'; } $risk = Str::headline($snapshot['risk'] ?? 'unknown'); $failed = (int) ($snapshot['recent_failed'] ?? 0); $total = (int) ($snapshot['recent_total'] ?? 0); return sprintf('%s (%d/%d)', $risk, $failed, $total); } private static function riskColor(?string $risk): string { return match ($risk) { 'high' => 'danger', 'medium' => 'warning', 'low' => 'success', default => 'gray', }; } }