Files
zemailnator/app/Filament/Resources/TrialExtensions/Schemas/TrialExtensionForm.php
idevakk 27ac13948c feat: implement comprehensive multi-provider payment processing system
- Add unified payment provider architecture with contract-based design
  - Implement 6 payment providers: Stripe, Lemon Squeezy, Polar, Oxapay, Crypto, Activation Keys
  - Create subscription management with lifecycle handling (create, cancel, pause, resume, update)
  - Add coupon system with usage tracking and trial extensions
  - Build Filament admin resources for payment providers, subscriptions, coupons, and trials
  - Implement payment orchestration service with provider registry and configuration management
  - Add comprehensive payment logging and webhook handling for all providers
  - Create customer analytics dashboard with revenue, churn, and lifetime value metrics
  - Add subscription migration service for provider switching
  - Include extensive test coverage for all payment functionality
2025-11-19 09:37:00 -08:00

180 lines
8.2 KiB
PHP

<?php
namespace App\Filament\Resources\TrialExtensions\Schemas;
use App\Models\Subscription;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
class TrialExtensionForm
{
public static function configure(Schema $schema): Schema
{
return $schema
->components([
Section::make('Trial Extension Details')
->schema([
Grid::make(2)
->schema([
Select::make('subscription_id')
->label('Subscription')
->options(function () {
return Subscription::with(['user', 'plan'])
->where('status', 'trialing')
->orWhere('status', 'active')
->get()
->mapWithKeys(function ($subscription) {
$label = "{$subscription->user->name} - {$subscription->plan->name}";
if ($subscription->trial_ends_at) {
$label .= " ({$subscription->trial_ends_at->format('M j, Y')})";
}
return [$subscription->id => $label];
})
->toArray();
})
->searchable()
->preload()
->required()
->live()
->afterStateUpdated(function ($state, callable $set, callable $get) {
if ($state) {
$subscription = Subscription::find($state);
if ($subscription) {
$set('user_id', $subscription->user_id);
$set('original_trial_ends_at', $subscription->trial_ends_at);
// Set user display name
if ($subscription->relationLoaded('user')) {
$set('user_display', $subscription->user->name);
} else {
$subscription->load('user');
$set('user_display', $subscription->user->name);
}
self::calculateNewTrialEndDate($set, $get);
}
}
}),
TextInput::make('user_display')
->label('User')
->disabled()
->dehydrated(false),
]),
// Hidden field for user_id to ensure it gets submitted
TextInput::make('user_id')
->hidden(),
Grid::make(2)
->schema([
TextInput::make('extension_days')
->label('Extension Days')
->numeric()
->required()
->default(7)
->live(onBlur: true)
->helperText('Number of days to extend the trial')
->afterStateUpdated(fn ($state, callable $set, callable $get) => self::calculateNewTrialEndDate($set, $get)),
Select::make('extension_type')
->label('Extension Type')
->options([
'manual' => 'Manual Grant',
'automatic' => 'Automatic Extension',
'compensation' => 'Compensation',
])
->default('manual')
->required(),
]),
Grid::make(2)
->schema([
DateTimePicker::make('original_trial_ends_at')
->label('Original Trial End Date')
->disabled()
->dehydrated(false),
DateTimePicker::make('new_trial_ends_at')
->label('New Trial End Date')
->required()
->readOnly()
->helperText('This will be calculated automatically based on extension days'),
]),
]),
Section::make('Additional Information')
->schema([
Textarea::make('reason')
->label('Reason')
->rows(3)
->helperText('Reason for granting this trial extension'),
Select::make('granted_by_admin_id')
->label('Granted By')
->relationship('grantedByAdmin', 'name')
->searchable()
->preload()
->default(fn () => auth()->id())
->helperText('Admin who granted this extension'),
])
->collapsible(),
Section::make('Timestamps')
->schema([
DateTimePicker::make('granted_at')
->label('Granted At')
->default(now())
->required(),
])
->collapsible(),
Section::make('Metadata')
->schema([
KeyValue::make('metadata')
->label('Custom Metadata')
->addActionLabel('Add metadata')
->keyLabel('Key')
->valueLabel('Value')
->helperText('Additional key-value data for this trial extension'),
])
->collapsible(),
]);
}
/**
* Calculate the new trial end date based on original date and extension days
*/
private static function calculateNewTrialEndDate(callable $set, callable $get): void
{
$subscriptionId = $get('subscription_id');
$extensionDays = (int) $get('extension_days') ?: 0;
if (! $subscriptionId || ! $extensionDays) {
$set('new_trial_ends_at', null);
return;
}
$subscription = Subscription::find($subscriptionId);
if (! $subscription) {
$set('new_trial_ends_at', null);
return;
}
// Use the original trial end date if available, otherwise use current date
$baseDate = $subscription->trial_ends_at ?: now();
// Calculate new end date by adding extension days
$newEndDate = $baseDate->copy()->addDays($extensionDays);
// Format for DateTimePicker (Y-m-d H:i:s format)
$set('new_trial_ends_at', $newEndDate->format('Y-m-d H:i:s'));
}
}