schema([ Select::make('tenant_id') ->label('Tenant') ->relationship('tenant', 'name') ->searchable() ->preload() ->nullable(), Select::make('event_id') ->label('Event') ->relationship('event', 'name') ->searchable() ->preload() ->nullable(), Select::make('package_id') ->label('Package') ->relationship('package', 'name') ->searchable() ->preload() ->required(), TextInput::make('provider_id') ->label('Provider ID') ->required() ->maxLength(255), TextInput::make('price') ->label('Price') ->numeric() ->step(0.01) ->prefix('€') ->required(), Select::make('type') ->label('Type') ->options([ 'endcustomer_event' => 'Endcustomer Event', 'reseller_subscription' => 'Reseller Subscription', ]) ->required(), Textarea::make('metadata') ->label('Metadata') ->json() ->columnSpanFull(), Toggle::make('refunded') ->label('Refunded') ->default(false), ]) ->columns(2); } public static function table(Table $table): Table { return $table ->columns([ BadgeColumn::make('type') ->label('Type') ->color(fn (string $state): string => match ($state) { 'endcustomer_event' => 'info', 'reseller_subscription' => 'success', default => 'gray', }), TextColumn::make('tenant.name') ->label('Tenant') ->searchable() ->sortable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('event.name') ->label('Event') ->searchable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('package.name') ->label('Package') ->badge() ->color('success'), TextColumn::make('price') ->label('Price') ->money('EUR') ->sortable(), TextColumn::make('purchased_at') ->dateTime() ->sortable(), BadgeColumn::make('refunded') ->label('Status') ->color(fn (bool $state): string => $state ? 'danger' : 'success'), TextColumn::make('provider_id') ->copyable() ->toggleable(), TextColumn::make('metadata.consents.legal_version') ->label('Legal Version') ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('metadata.consents.accepted_terms_at') ->label('Terms accepted') ->dateTime() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('metadata.consents.accepted_withdrawal_notice_at') ->label('Withdrawal notice') ->dateTime() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('metadata.consents.digital_content_waiver_at') ->label('Waiver (digital)') ->dateTime() ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ SelectFilter::make('type') ->options([ 'endcustomer_event' => 'Endcustomer Event', 'reseller_subscription' => 'Reseller Subscription', ]), Filter::make('purchased_at') ->form([ DateTimePicker::make('started_from'), DateTimePicker::make('ended_before'), ]) ->query(function (Builder $query, array $data): Builder { return $query ->when( $data['started_from'], fn (Builder $query, $date): Builder => $query->whereDate('purchased_at', '>=', $date), ) ->when( $data['ended_before'], fn (Builder $query, $date): Builder => $query->whereDate('purchased_at', '<=', $date), ); }), SelectFilter::make('tenant_id') ->label('Tenant') ->relationship('tenant', 'name') ->searchable(), ]) ->actions([ ViewAction::make(), EditAction::make() ->after(fn (array $data, PackagePurchase $record) => app(SuperAdminAuditLogger::class)->recordModelMutation( 'updated', $record, SuperAdminAuditLogger::fieldsMetadata($data), static::class )), Action::make('refund') ->label('Refund') ->color('danger') ->icon('heroicon-o-arrow-uturn-left') ->requiresConfirmation() ->visible(fn (PackagePurchase $record): bool => ! $record->refunded) ->form([ Textarea::make('reason') ->label('Refund reason (optional)') ->rows(2), ]) ->action(function (PackagePurchase $record, array $data) { $reason = $data['reason'] ?? null; $refundSuccess = true; $errorMessage = null; if ($record->provider === 'paddle' && $record->provider_id) { try { /** @var PaddleTransactionService $paddle */ $paddle = App::make(PaddleTransactionService::class); $paddle->refund($record->provider_id, ['reason' => $reason]); } catch (\Throwable $exception) { $refundSuccess = false; $errorMessage = $exception->getMessage(); Log::warning('Paddle refund failed', [ 'purchase_id' => $record->id, 'provider_id' => $record->provider_id, 'error' => $exception->getMessage(), ]); } } $metadata = $record->metadata ?? []; $metadata['refund_reason'] = $reason ?: ($metadata['refund_reason'] ?? null); $record->update([ 'refunded' => true, 'metadata' => $metadata, ]); Log::info('Refund processed for purchase ID: '.$record->id, [ 'provider' => $record->provider, 'provider_id' => $record->provider_id, 'reason' => $reason, ]); $customerEmail = $record->tenant->contact_email ?? $record->tenant?->user?->email; if ($customerEmail) { Notification::route('mail', $customerEmail)->notify(new RefundReceipt($record, $reason)); } $opsEmail = config('mail.ops_address'); if ($opsEmail) { Notification::route('mail', $opsEmail)->notify(new RefundProcessed($record, $refundSuccess, $reason, $errorMessage)); } app(SuperAdminAuditLogger::class)->record( 'purchase.refunded', $record, SuperAdminAuditLogger::fieldsMetadata(['refunded', 'metadata']), source: static::class ); }), ]) ->bulkActions([ BulkActionGroup::make([ DeleteBulkAction::make() ->after(function (Collection $records): void { $logger = app(SuperAdminAuditLogger::class); foreach ($records as $record) { $logger->recordModelMutation( 'deleted', $record, source: static::class ); } }), ]), ]) ->emptyStateHeading('No Purchases Found') ->emptyStateDescription('Create your first purchase.'); } public static function getPages(): array { return [ 'index' => Pages\ListPurchases::route('/'), 'create' => Pages\CreatePurchase::route('/create'), 'view' => Pages\ViewPurchase::route('/{record}'), 'edit' => Pages\EditPurchase::route('/{record}/edit'), ]; } public static function getRelations(): array { return [ // Add RelationManagers if needed ]; } }