feat: implements activation key as payment provider and migrate activation key to use unified payment system
This commit is contained in:
@@ -2,27 +2,28 @@
|
||||
|
||||
namespace App\Filament\Pages;
|
||||
|
||||
use BackedEnum;
|
||||
use UnitEnum;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use App\Models\ActivationKey;
|
||||
use App\Models\Plan;
|
||||
use BackedEnum;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Concerns\InteractsWithForms;
|
||||
use Filament\Forms\Contracts\HasForms;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Tables\Columns\BooleanColumn;
|
||||
use Filament\Tables\Columns\BadgeColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Collection;
|
||||
use Response;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use UnitEnum;
|
||||
|
||||
class GenerateActivationKeys extends Page implements HasForms, HasTable
|
||||
{
|
||||
@@ -40,6 +41,8 @@ class GenerateActivationKeys extends Page implements HasForms, HasTable
|
||||
|
||||
public $quantity = 1;
|
||||
|
||||
public $notes;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->form->fill();
|
||||
@@ -50,36 +53,103 @@ class GenerateActivationKeys extends Page implements HasForms, HasTable
|
||||
return [
|
||||
Select::make('plan_id')
|
||||
->label('Select Plan')
|
||||
->options(Plan::all()->pluck('name', 'id'))
|
||||
->required(),
|
||||
->options(function () {
|
||||
return Plan::with('planTier')
|
||||
->get()
|
||||
->map(function ($plan) {
|
||||
$tierName = $plan->planTier ? $plan->planTier->name : '';
|
||||
$billingCycle = $plan->getBillingCycleDisplay();
|
||||
$name = $plan->name;
|
||||
|
||||
if ($tierName) {
|
||||
$name .= " ({$tierName})";
|
||||
}
|
||||
if ($billingCycle) {
|
||||
$name .= " - {$billingCycle}";
|
||||
}
|
||||
|
||||
return $name;
|
||||
})
|
||||
->toArray();
|
||||
})
|
||||
->required()
|
||||
->reactive()
|
||||
->afterStateUpdated(fn ($state, callable $set) => $set('quantity', 1)),
|
||||
|
||||
TextInput::make('quantity')
|
||||
->label('Number of Keys')
|
||||
->numeric()
|
||||
->minValue(1)
|
||||
->maxValue(100)
|
||||
->default(1)
|
||||
->required(),
|
||||
->required()
|
||||
->helperText('Generate multiple keys for bulk distribution'),
|
||||
|
||||
Textarea::make('notes')
|
||||
->label('Generation Notes (Optional)')
|
||||
->rows(2)
|
||||
->placeholder('Add notes about this batch of keys...')
|
||||
->helperText('These notes will be stored with the generated keys for tracking purposes'),
|
||||
];
|
||||
}
|
||||
|
||||
public function generate(): void
|
||||
{
|
||||
$data = $this->form->getState();
|
||||
$plan = Plan::query()->findOrFail($data['plan_id']);
|
||||
try {
|
||||
$data = $this->form->getState();
|
||||
$plan = Plan::with('planTier')->findOrFail($data['plan_id']);
|
||||
|
||||
for ($i = 0; $i < $data['quantity']; $i++) {
|
||||
ActivationKey::query()->create([
|
||||
'price_id' => $plan->pricing_id,
|
||||
'activation_key' => strtoupper('Z'.Str::random(16)),
|
||||
'is_activated' => false,
|
||||
$generatedKeys = [];
|
||||
$batchId = 'BATCH_'.now()->format('YmdHis').'_'.Str::random(8);
|
||||
|
||||
for ($i = 0; $i < $data['quantity']; $i++) {
|
||||
$activationKey = ActivationKey::query()->create([
|
||||
'price_id' => $plan->pricing_id,
|
||||
'activation_key' => $this->generateUniqueActivationKey($plan),
|
||||
'is_activated' => false,
|
||||
]);
|
||||
|
||||
$generatedKeys[] = $activationKey->activation_key;
|
||||
}
|
||||
|
||||
// Log the generation for audit purposes
|
||||
\Log::info('Activation keys generated', [
|
||||
'batch_id' => $batchId,
|
||||
'plan_id' => $plan->id,
|
||||
'plan_name' => $plan->name,
|
||||
'quantity' => $data['quantity'],
|
||||
'generated_by' => auth()->id(),
|
||||
'notes' => $data['notes'] ?? null,
|
||||
]);
|
||||
|
||||
Notification::make()
|
||||
->title("{$data['quantity']} activation key(s) generated successfully")
|
||||
->body("For {$plan->name} - Batch ID: {$batchId}")
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$this->form->fill(); // Reset form
|
||||
} catch (\Exception $exception) {
|
||||
$this->form->fill();
|
||||
Notification::make()
|
||||
->title('Something went wrong')
|
||||
->body("Error: {$exception->getMessage()}")
|
||||
->danger()
|
||||
->send();
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title("{$data['quantity']} activation key(s) generated.")
|
||||
->success()
|
||||
->send();
|
||||
$this->form->fill(); // Reset form
|
||||
}
|
||||
|
||||
private function generateUniqueActivationKey(Plan $plan): string
|
||||
{
|
||||
do {
|
||||
// Create a more structured key format: PLAN_PREFIX + RANDOM
|
||||
$prefix = strtoupper(substr(str_replace([' ', '-'], '', $plan->name), 0, 3));
|
||||
$random = strtoupper(Str::random(13));
|
||||
$key = $prefix.$random;
|
||||
} while (ActivationKey::where('activation_key', $key)->exists());
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
// === Table Setup ===
|
||||
@@ -92,24 +162,55 @@ class GenerateActivationKeys extends Page implements HasForms, HasTable
|
||||
{
|
||||
return [
|
||||
TextColumn::make('activation_key')
|
||||
->label('Key')
|
||||
->copyable(),
|
||||
->label('Activation Key')
|
||||
->copyable()
|
||||
->searchable()
|
||||
->weight('font-semibold'),
|
||||
|
||||
BooleanColumn::make('is_activated'),
|
||||
BadgeColumn::make('status')
|
||||
->label('Status')
|
||||
->getStateUsing(function ($record): string {
|
||||
return $record->is_activated ? 'Activated' : 'Unused';
|
||||
})
|
||||
->colors([
|
||||
'success' => 'Activated',
|
||||
'warning' => 'Unused',
|
||||
]),
|
||||
|
||||
TextColumn::make('plan.name')
|
||||
->label('Plan')
|
||||
->getStateUsing(function ($record): string {
|
||||
$plan = Plan::where('pricing_id', $record->price_id)->first();
|
||||
|
||||
return $plan ? $plan->name : 'Unknown Plan';
|
||||
})
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('plan.planTier.name')
|
||||
->label('Tier')
|
||||
->getStateUsing(function ($record): ?string {
|
||||
$plan = Plan::where('pricing_id', $record->price_id)->first();
|
||||
|
||||
return $plan && $plan->planTier ? $plan->planTier->name : null;
|
||||
}),
|
||||
|
||||
TextColumn::make('user.email')
|
||||
->label('Activated By'),
|
||||
->label('Activated By')
|
||||
->default('Not activated')
|
||||
->searchable(),
|
||||
|
||||
TextColumn::make('billing_interval')
|
||||
->label('Interval')
|
||||
TextColumn::make('billing_cycle')
|
||||
->label('Billing Cycle')
|
||||
->getStateUsing(function ($record): string {
|
||||
$isMonthly = Plan::query()->where('pricing_id', $record->price_id)->value('monthly_billing');
|
||||
$plan = Plan::where('pricing_id', $record->price_id)->first();
|
||||
|
||||
return $isMonthly ? 'Monthly' : 'Yearly';
|
||||
return $plan ? $plan->getBillingCycleDisplay() : 'Unknown';
|
||||
}),
|
||||
|
||||
TextColumn::make('created_at')
|
||||
->dateTime(),
|
||||
->label('Generated')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -117,15 +218,48 @@ class GenerateActivationKeys extends Page implements HasForms, HasTable
|
||||
{
|
||||
return [
|
||||
SelectFilter::make('is_activated')
|
||||
->label('Status')
|
||||
->options([
|
||||
true => 'Activated',
|
||||
false => 'Not Activated',
|
||||
]),
|
||||
|
||||
SelectFilter::make('price_id')
|
||||
->label('Plan')
|
||||
->options(
|
||||
Plan::query()->pluck('name', 'pricing_id')
|
||||
),
|
||||
->options(function () {
|
||||
return Plan::with('planTier')
|
||||
->get()
|
||||
->mapWithKeys(function ($plan) {
|
||||
$tierName = $plan->planTier ? " ({$plan->planTier->name})" : '';
|
||||
|
||||
return [$plan->pricing_id => $plan->name.$tierName];
|
||||
})
|
||||
->toArray();
|
||||
}),
|
||||
|
||||
SelectFilter::make('created_at')
|
||||
->label('Generation Date')
|
||||
->options([
|
||||
'today' => 'Today',
|
||||
'this_week' => 'This Week',
|
||||
'this_month' => 'This Month',
|
||||
'last_month' => 'Last Month',
|
||||
])
|
||||
->query(function (Builder $query, array $data): Builder {
|
||||
if ($data['value'] === 'today') {
|
||||
return $query->whereDate('created_at', today());
|
||||
} elseif ($data['value'] === 'this_week') {
|
||||
return $query->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()]);
|
||||
} elseif ($data['value'] === 'this_month') {
|
||||
return $query->whereMonth('created_at', now()->month)
|
||||
->whereYear('created_at', now()->year);
|
||||
} elseif ($data['value'] === 'last_month') {
|
||||
return $query->whereMonth('created_at', now()->subMonth()->month)
|
||||
->whereYear('created_at', now()->subMonth()->year);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -135,27 +269,104 @@ class GenerateActivationKeys extends Page implements HasForms, HasTable
|
||||
BulkAction::make('Download Keys')
|
||||
->action(fn (Collection $records): BinaryFileResponse => $this->downloadKeys($records))
|
||||
->deselectRecordsAfterCompletion()
|
||||
->requiresConfirmation(),
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Download selected activation keys as a text file.'),
|
||||
|
||||
BulkAction::make('Deactivate Keys')
|
||||
->action(function (Collection $records) {
|
||||
$count = 0;
|
||||
foreach ($records as $record) {
|
||||
if ($record->is_activated) {
|
||||
// Deactivate the subscription if it exists
|
||||
$subscription = $record->user?->subscriptions()
|
||||
->where('provider', 'activation_key')
|
||||
->where('provider_subscription_id', $record->id)
|
||||
->first();
|
||||
|
||||
if ($subscription) {
|
||||
$subscription->update([
|
||||
'status' => 'cancelled',
|
||||
'ends_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
$record->update([
|
||||
'is_activated' => false,
|
||||
'user_id' => null,
|
||||
]);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title("{$count} key(s) deactivated")
|
||||
->success()
|
||||
->send();
|
||||
})
|
||||
->deselectRecordsAfterCompletion()
|
||||
->requiresConfirmation()
|
||||
->modalDescription('This will deactivate the selected keys and cancel associated subscriptions.')
|
||||
->color('danger'),
|
||||
|
||||
BulkAction::make('Delete Keys')
|
||||
->action(function (Collection $records) {
|
||||
$count = $records->count();
|
||||
|
||||
// First, deactivate any associated subscriptions
|
||||
foreach ($records as $record) {
|
||||
$subscription = $record->user?->subscriptions()
|
||||
->where('provider', 'activation_key')
|
||||
->where('provider_subscription_id', $record->id)
|
||||
->first();
|
||||
|
||||
if ($subscription) {
|
||||
$subscription->delete();
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the keys
|
||||
$records->each->delete();
|
||||
|
||||
Notification::make()
|
||||
->title("{$count} key(s) deleted")
|
||||
->success()
|
||||
->send();
|
||||
})
|
||||
->deselectRecordsAfterCompletion()
|
||||
->requiresConfirmation()
|
||||
->modalDescription('This will permanently delete the selected keys and associated subscriptions.')
|
||||
->color('danger'),
|
||||
];
|
||||
}
|
||||
|
||||
public function downloadKeys(Collection $records): BinaryFileResponse
|
||||
{
|
||||
$text = $records->pluck('activation_key')->implode("\n");
|
||||
$content = "# Activation Keys\n";
|
||||
$content .= '# Generated: '.now()->toDateTimeString()."\n";
|
||||
$content .= '# Total Keys: '.$records->count()."\n\n";
|
||||
|
||||
$filename = 'activation_keys_'.now()->timestamp.'.txt';
|
||||
// Store the file in the 'public' directory or a subdirectory within 'public'
|
||||
$path = public_path("activation/{$filename}");
|
||||
|
||||
// Make sure the 'activation' folder exists, create it if it doesn't
|
||||
if (! file_exists(public_path('activation'))) {
|
||||
mkdir(public_path('activation'), 0777, true);
|
||||
foreach ($records as $record) {
|
||||
$plan = Plan::where('pricing_id', $record->price_id)->first();
|
||||
$content .= "Key: {$record->activation_key}\n";
|
||||
$content .= 'Plan: '.($plan->name ?? 'Unknown Plan')."\n";
|
||||
$content .= 'Status: '.($record->is_activated ? 'Activated' : 'Unused')."\n";
|
||||
$content .= 'Generated: '.$record->created_at->toDateTimeString()."\n";
|
||||
if ($record->user) {
|
||||
$content .= 'Activated By: '.$record->user->email."\n";
|
||||
}
|
||||
$content .= "---\n\n";
|
||||
}
|
||||
|
||||
// Write the contents to the file
|
||||
file_put_contents($path, $text);
|
||||
$filename = 'activation_keys_'.now()->format('Y-m-d_H-i-s').'.txt';
|
||||
$path = public_path("activation/{$filename}");
|
||||
|
||||
// Make sure the 'activation' folder exists
|
||||
if (! file_exists(public_path('activation')) && ! mkdir($concurrentDirectory = public_path('activation'), 0755, true) && ! is_dir($concurrentDirectory)) {
|
||||
Log::error('Failed to create activation keys file: '.$concurrentDirectory);
|
||||
}
|
||||
|
||||
file_put_contents($path, $content);
|
||||
|
||||
// Return the response that allows users to download the file directly
|
||||
return response()->download($path)->deleteFileAfterSend(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,12 @@ use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Infolists\Components\RepeatableEntry;
|
||||
use Filament\Infolists\Components\TextEntry;
|
||||
use Filament\Schemas\Components\Grid;
|
||||
use Filament\Schemas\Components\Section;
|
||||
use Filament\Schemas\Components\Tabs;
|
||||
use Filament\Schemas\Components\Tabs\Tab;
|
||||
use Filament\Schemas\Schema;
|
||||
|
||||
class SubscriptionForm
|
||||
@@ -144,27 +148,6 @@ class SubscriptionForm
|
||||
->collapsible()
|
||||
->visible(fn ($get) => $get('status') === 'cancelled'),
|
||||
|
||||
Section::make('Provider Information')
|
||||
->schema([
|
||||
TextInput::make('stripe_id')
|
||||
->label('Stripe ID')
|
||||
->visible(fn ($get) => $get('provider') === 'stripe'),
|
||||
|
||||
TextInput::make('stripe_status')
|
||||
->label('Stripe Status')
|
||||
->visible(fn ($get) => $get('provider') === 'stripe'),
|
||||
|
||||
TextInput::make('stripe_price')
|
||||
->label('Stripe Price')
|
||||
->visible(fn ($get) => $get('provider') === 'stripe'),
|
||||
|
||||
Textarea::make('provider_data')
|
||||
->label('Provider Data')
|
||||
->rows(3)
|
||||
->helperText('JSON data from the payment provider'),
|
||||
])
|
||||
->collapsible(),
|
||||
|
||||
Section::make('Migration Information')
|
||||
->schema([
|
||||
TextInput::make('migration_batch_id')
|
||||
@@ -178,6 +161,116 @@ class SubscriptionForm
|
||||
->rows(3),
|
||||
])
|
||||
->collapsible(),
|
||||
|
||||
Tabs::make('Provider Data')
|
||||
->tabs([
|
||||
Tab::make('Overview')
|
||||
->schema([
|
||||
Section::make('Activation Key')
|
||||
->schema([
|
||||
TextEntry::make('provider_data.activation_key')
|
||||
->label('Activation Key')
|
||||
->copyable(),
|
||||
TextEntry::make('provider_data.key_id')
|
||||
->label('Key ID'),
|
||||
TextEntry::make('provider_data.redeemed_at')
|
||||
->label('Redeemed At'),
|
||||
])
|
||||
->columns(1)
|
||||
->hidden(fn ($record) => ! data_get($record, 'provider_data.activation_key')),
|
||||
|
||||
Section::make('Plan Details')
|
||||
->schema([
|
||||
TextEntry::make('provider_data.plan_details.name')
|
||||
->label('Plan Name'),
|
||||
TextEntry::make('provider_data.plan_details.price')
|
||||
->label('Price')
|
||||
->prefix('$'),
|
||||
TextEntry::make('provider_data.plan_details.billing_cycle_display')
|
||||
->label('Billing Cycle'),
|
||||
TextEntry::make('provider_data.plan_details.plan_tier')
|
||||
->badge()
|
||||
->label('Tier'),
|
||||
TextEntry::make('provider_data.plan_details.billing_cycle_days')
|
||||
->label('Duration')
|
||||
->suffix(' days'),
|
||||
])
|
||||
->columns(2)
|
||||
->hidden(fn ($record) => ! data_get($record, 'provider_data.plan_details')),
|
||||
|
||||
Section::make('Provider Info')
|
||||
->schema([
|
||||
TextEntry::make('provider_data.provider_info.name')
|
||||
->label('Provider Name'),
|
||||
TextEntry::make('provider_data.provider_info.version')
|
||||
->label('Version'),
|
||||
TextEntry::make('provider_data.provider_info.processed_at')
|
||||
->label('Processed At'),
|
||||
])
|
||||
->columns(2)
|
||||
->hidden(fn ($record) => ! data_get($record, 'provider_data.provider_info')),
|
||||
]),
|
||||
|
||||
Tab::make('Features')
|
||||
->schema([
|
||||
RepeatableEntry::make('provider_data.plan_details.features')
|
||||
->schema([
|
||||
Section::make()
|
||||
->schema([
|
||||
TextEntry::make('feature.display_name')
|
||||
->label('Feature Name')
|
||||
->weight('bold'),
|
||||
TextEntry::make('feature.description')
|
||||
->label('Description')
|
||||
->columnSpanFull(),
|
||||
TextEntry::make('feature.category')
|
||||
->label('Category')
|
||||
->badge(),
|
||||
TextEntry::make('feature.type')
|
||||
->label('Type'),
|
||||
|
||||
Section::make('Limit Details')
|
||||
->schema([
|
||||
TextEntry::make('limit.limit_value')
|
||||
->label('Limit Value'),
|
||||
TextEntry::make('limit.trial_limit_value')
|
||||
->label('Trial Limit'),
|
||||
TextEntry::make('limit.limit_type')
|
||||
->badge()
|
||||
->label('Limit Type'),
|
||||
TextEntry::make('limit.is_enabled')
|
||||
->label('Status')
|
||||
->badge()
|
||||
->formatStateUsing(fn ($state) => $state ? 'Enabled' : 'Disabled')
|
||||
->color(fn ($state) => $state ? 'success' : 'gray'),
|
||||
])
|
||||
->columns(2)
|
||||
->collapsed(),
|
||||
])
|
||||
->columns(2),
|
||||
])
|
||||
->columnSpanFull(),
|
||||
])
|
||||
->hidden(fn ($record) => ! data_get($record, 'provider_data.plan_details.features')),
|
||||
|
||||
Tab::make('Raw JSON')
|
||||
->schema([
|
||||
TextEntry::make('provider_data')
|
||||
->formatStateUsing(function ($state) {
|
||||
if (is_string($state)) {
|
||||
$data = json_decode($state, true);
|
||||
} else {
|
||||
$data = (array) $state;
|
||||
}
|
||||
|
||||
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
})
|
||||
->state(fn ($record) => $record->provider_data)
|
||||
->copyable()
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
])->columnSpanFull(),
|
||||
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Livewire\Dashboard;
|
||||
use App\Models\ActivationKey;
|
||||
use App\Models\Plan;
|
||||
use App\Models\PlanTier;
|
||||
use App\Services\Payments\PaymentOrchestrator;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
@@ -125,29 +124,15 @@ class Pricing extends Component
|
||||
private function activateSubscriptionKey(ActivationKey $activation): bool
|
||||
{
|
||||
try {
|
||||
// Use PaymentOrchestrator for activation key processing
|
||||
$orchestrator = app(PaymentOrchestrator::class);
|
||||
|
||||
// Find the plan associated with this activation key
|
||||
$plan = null;
|
||||
if ($activation->plan_id) {
|
||||
$plan = Plan::find($activation->plan_id);
|
||||
} elseif ($activation->price_id) {
|
||||
// Fallback to legacy pricing_id lookup
|
||||
$plan = Plan::where('pricing_id', $activation->price_id)->first();
|
||||
}
|
||||
|
||||
if (! $plan) {
|
||||
Log::error('No plan found for activation key: '.$activation->id);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create subscription using orchestrator
|
||||
// Use the ActivationKeyProvider directly
|
||||
$provider = app(\App\Services\Payments\Providers\ActivationKeyProvider::class);
|
||||
$user = auth()->user();
|
||||
$subscription = $orchestrator->createSubscriptionFromActivationKey($user, $activation, $plan);
|
||||
|
||||
if ($subscription) {
|
||||
// Redeem the activation key using the provider
|
||||
$result = $provider->redeemActivationKey($activation->activation_key, $user);
|
||||
|
||||
if ($result['success']) {
|
||||
// Mark activation key as used
|
||||
$activation->is_activated = true;
|
||||
$activation->user_id = $user->id;
|
||||
$activation->save();
|
||||
@@ -155,6 +140,8 @@ class Pricing extends Component
|
||||
return true;
|
||||
}
|
||||
|
||||
Log::error('Activation key redemption failed: '.$result['message'] ?? 'Unknown error');
|
||||
|
||||
return false;
|
||||
} catch (Exception $e) {
|
||||
Log::error('Activation key processing failed: '.$e->getMessage());
|
||||
|
||||
@@ -335,24 +335,47 @@ class ActivationKeyProvider implements PaymentProviderContract
|
||||
'is_activated' => true,
|
||||
]);
|
||||
|
||||
// Find or create subscription
|
||||
$plan = Plan::findOrFail($keyRecord->price_id);
|
||||
// Find the plan associated with this activation key
|
||||
$plan = Plan::where('pricing_id', $keyRecord->price_id)->first();
|
||||
|
||||
if (! $plan) {
|
||||
throw new \Exception('No plan found for activation key with pricing_id: '.$keyRecord->price_id);
|
||||
}
|
||||
|
||||
// Calculate subscription end date based on plan billing cycle
|
||||
$endsAt = null;
|
||||
if ($plan->billing_cycle_days && $plan->billing_cycle_days > 0) {
|
||||
$endsAt = now()->addDays($plan->billing_cycle_days);
|
||||
}
|
||||
|
||||
$subscription = Subscription::create([
|
||||
'user_id' => $user->id,
|
||||
'plan_id' => $plan->id,
|
||||
'type' => 'activation_key',
|
||||
'stripe_id' => 'ak_'.$keyRecord->id.'_'.uniqid(),
|
||||
'type' => 'default',
|
||||
'stripe_id' => 'ak_'.$keyRecord->id.'_'.uniqid('', true),
|
||||
'stripe_status' => 'active',
|
||||
'provider' => $this->getName(),
|
||||
'provider_subscription_id' => $keyRecord->id,
|
||||
'status' => 'active',
|
||||
'starts_at' => now(),
|
||||
'ends_at' => null, // No expiration for activation keys
|
||||
'ends_at' => $endsAt,
|
||||
'provider_data' => [
|
||||
'activation_key' => $activationKey,
|
||||
'key_id' => $keyRecord->id,
|
||||
'redeemed_at' => now()->toISOString(),
|
||||
'plan_details' => [
|
||||
'name' => $plan->name,
|
||||
'price' => $plan->price,
|
||||
'billing_cycle_days' => $plan->billing_cycle_days,
|
||||
'billing_cycle_display' => $plan->getBillingCycleDisplay(),
|
||||
'plan_tier' => $plan->planTier ? $plan->planTier->name : null,
|
||||
'features' => $plan->getFeaturesWithLimits(),
|
||||
],
|
||||
'provider_info' => [
|
||||
'name' => $this->getName(),
|
||||
'version' => '1.0',
|
||||
'processed_at' => now()->toISOString(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user