Files
fotospiel-app/app/Filament/Resources/Coupons/RelationManagers/RedemptionsRelationManager.php
Codex Agent 41ed682fbe
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Add coupon fraud context and analytics tracking
2026-01-02 23:31:26 +01:00

124 lines
4.8 KiB
PHP

<?php
namespace App\Filament\Resources\Coupons\RelationManagers;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Support\Str;
class RedemptionsRelationManager extends RelationManager
{
protected static string $relationship = 'redemptions';
public function form(Schema $schema): Schema
{
return $schema;
}
public function table(Table $table): Table
{
return $table
->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',
};
}
}