config = array_merge([ 'key_prefix' => 'AK-', 'key_length' => 32, 'expiration_days' => null, // null means no expiration 'success_url' => route('payment.success'), 'cancel_url' => route('payment.cancel'), ], $config); } public function getName(): string { return 'activation_key'; } public function isActive(): bool { return true; // Activation keys are always available } public function createSubscription(User $user, Plan $plan, array $options = []): array { try { DB::beginTransaction(); // Generate a unique activation key $activationKey = $this->generateUniqueActivationKey(); // Create activation key record $keyRecord = ActivationKey::create([ 'user_id' => $user->id, 'activation_key' => $activationKey, 'price_id' => $plan->id, 'is_activated' => false, ]); // Create subscription record $subscription = Subscription::create([ 'user_id' => $user->id, 'plan_id' => $plan->id, 'type' => 'activation_key', 'stripe_id' => 'ak_'.$keyRecord->id.'_'.uniqid(more_entropy: true), // Use activation key ID + unique ID for compatibility 'stripe_status' => 'pending_activation', 'provider' => $this->getName(), 'provider_subscription_id' => $keyRecord->id, 'status' => 'pending_activation', 'starts_at' => null, 'ends_at' => null, 'provider_data' => [ 'activation_key' => $activationKey, 'key_id' => $keyRecord->id, 'created_at' => now()->toISOString(), ], ]); DB::commit(); // Notify activation key generated $this->notifyActivationKeyGenerated($user, $plan, $activationKey); return [ 'provider_subscription_id' => $keyRecord->id, 'status' => 'pending_activation', 'activation_key' => $activationKey, 'plan_name' => $plan->name, 'plan_price' => $plan->price, 'type' => 'activation_key', 'message' => 'Activation key generated. User needs to redeem the key to activate the subscription.', ]; } catch (\Exception $e) { DB::rollBack(); Log::error('Activation key subscription creation failed', [ 'user_id' => $user->id, 'plan_id' => $plan->id, 'error' => $e->getMessage(), ]); throw $e; } } public function cancelSubscription(Subscription $subscription, string $reason = ''): bool { try { // For activation keys, we don't actually cancel since it's a one-time activation // We can deactivate the subscription if needed $subscription->update([ 'status' => 'cancelled', 'cancelled_at' => now(), 'cancellation_reason' => $reason, ]); // Notify subscription cancelled $this->notifySubscriptionCancelled($subscription, $reason ?: 'Manual cancellation'); return true; } catch (\Exception $e) { Log::error('Activation key subscription cancellation failed', [ 'subscription_id' => $subscription->id, 'error' => $e->getMessage(), ]); throw $e; } } public function updateSubscription(Subscription $subscription, Plan $newPlan): array { try { // Activation keys don't support plan updates // User would need a new activation key for a different plan throw new \Exception('Activation keys do not support plan updates'); } catch (\Exception $e) { Log::error('Activation key subscription update failed', [ 'subscription_id' => $subscription->id, 'new_plan_id' => $newPlan->id, 'error' => $e->getMessage(), ]); throw $e; } } public function pauseSubscription(Subscription $subscription): bool { // Activation keys can't be paused return false; } public function resumeSubscription(Subscription $subscription): bool { // Activation keys can't be paused, so can't be resumed return false; } public function getSubscriptionDetails(string $providerSubscriptionId): array { try { $activationKey = ActivationKey::findOrFail($providerSubscriptionId); return [ 'id' => $activationKey->id, 'activation_key' => $activationKey->activation_key, 'user_id' => $activationKey->user_id, 'price_id' => $activationKey->price_id, 'is_activated' => $activationKey->is_activated, 'created_at' => $activationKey->created_at->toISOString(), 'updated_at' => $activationKey->updated_at->toISOString(), ]; } catch (\Exception $e) { Log::error('Activation key subscription details retrieval failed', [ 'subscription_id' => $providerSubscriptionId, 'error' => $e->getMessage(), ]); throw $e; } } public function createCheckoutSession(User $user, Plan $plan, array $options = []): array { return $this->createSubscription($user, $plan, $options); } public function createCustomerPortalSession(User $user): array { return [ 'portal_url' => route('dashboard'), 'message' => 'Activation keys are managed through your dashboard', ]; } public function processWebhook(Request $request): array { // Activation keys don't have webhooks return [ 'event_type' => 'not_applicable', 'processed' => false, 'data' => [], ]; } public function validateWebhook(Request $request): bool { // No webhooks to validate return false; } public function getConfiguration(): array { return $this->config; } public function syncSubscriptionStatus(Subscription $subscription): array { return $this->getSubscriptionDetails($subscription->provider_subscription_id); } public function getPaymentMethodDetails(string $paymentMethodId): array { try { $activationKey = ActivationKey::findOrFail($paymentMethodId); return [ 'id' => $activationKey->id, 'type' => 'activation_key', 'activation_key' => $activationKey->activation_key, 'is_activated' => $activationKey->is_activated, 'created_at' => $activationKey->created_at->toISOString(), ]; } catch (\Exception $e) { Log::error('Activation key payment method details retrieval failed', [ 'payment_method_id' => $paymentMethodId, 'error' => $e->getMessage(), ]); throw $e; } } public function processRefund(string $paymentId, float $amount, string $reason = ''): array { // Activation keys are not refundable throw new \Exception('Activation keys are not refundable'); } public function getTransactionHistory(User $user, array $filters = []): array { try { $query = ActivationKey::where('user_id', $user->id); // Apply filters if (isset($filters['status'])) { if ($filters['status'] === 'activated') { $query->where('is_activated', true); } elseif ($filters['status'] === 'unactivated') { $query->where('is_activated', false); } } if (isset($filters['date_from'])) { $query->where('created_at', '>=', $filters['date_from']); } if (isset($filters['date_to'])) { $query->where('created_at', '<=', $filters['date_to']); } $activationKeys = $query->orderBy('created_at', 'desc')->get(); $transactions = []; foreach ($activationKeys as $key) { $transactions[] = [ 'id' => $key->id, 'activation_key' => $key->activation_key, 'plan_id' => $key->price_id, 'is_activated' => $key->is_activated, 'created_at' => $key->created_at->toISOString(), 'updated_at' => $key->updated_at->toISOString(), ]; } return $transactions; } catch (\Exception $e) { Log::error('Activation key transaction history retrieval failed', [ 'user_id' => $user->id, 'error' => $e->getMessage(), ]); throw $e; } } public function calculateFees(float $amount): array { // Activation keys have no fees return [ 'fixed_fee' => 0, 'percentage_fee' => 0, 'total_fee' => 0, 'net_amount' => $amount, ]; } public function getSupportedCurrencies(): array { return ['USD']; // Activation keys are currency-agnostic } public function supportsRecurring(): bool { return false; // Activation keys are one-time } public function supportsOneTime(): bool { return true; } // Helper methods protected function generateUniqueActivationKey(): string { do { $key = $this->config['key_prefix'].strtoupper(Str::random($this->config['key_length'])); } while (ActivationKey::where('activation_key', $key)->exists()); return $key; } // Public method for redeeming activation keys public function redeemActivationKey(string $activationKey, User $user): array { try { DB::beginTransaction(); $keyRecord = ActivationKey::where('activation_key', $activationKey) ->where('is_activated', false) ->firstOrFail(); // Mark key as activated and assign to user $keyRecord->update([ 'user_id' => $user->id, 'is_activated' => true, ]); // 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' => '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' => $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(), ], ], ]); DB::commit(); // Notify activation key redeemed and subscription activated $this->notifyActivationKeyRedeemed($user, $plan, $activationKey); $this->notifySubscriptionActivated($user, $plan, $subscription); return [ 'success' => true, 'subscription_id' => $subscription->id, 'plan_name' => $plan->name, 'message' => 'Activation key redeemed successfully', ]; } catch (\Exception $e) { DB::rollBack(); Log::error('Activation key redemption failed', [ 'activation_key' => $activationKey, 'user_id' => $user->id, 'error' => $e->getMessage(), ]); throw $e; } } // Additional interface methods public function getSubscriptionMetadata(Subscription $subscription): array { $keyDetails = $this->getSubscriptionDetails($subscription->provider_subscription_id); return $keyDetails + [ 'redeemed_at' => $subscription->provider_data['redeemed_at'] ?? null, ]; } public function updateSubscriptionMetadata(Subscription $subscription, array $metadata): bool { try { $subscription->update([ 'provider_data' => array_merge($subscription->provider_data ?? [], $metadata), ]); return true; } catch (\Exception $e) { Log::error('Failed to update activation key subscription metadata', [ 'subscription_id' => $subscription->id, 'error' => $e->getMessage(), ]); return false; } } public function startTrial(Subscription $subscription, int $trialDays): bool { // Activation keys don't support trials return false; } public function applyCoupon(Subscription $subscription, string $couponCode): array { // Activation keys don't support coupons throw new \Exception('Coupons not supported for activation keys'); } public function removeCoupon(Subscription $subscription): bool { return false; // No coupons to remove } public function getUpcomingInvoice(Subscription $subscription): array { // Activation keys don't have invoices return [ 'amount_due' => 0, 'currency' => 'USD', 'next_payment_date' => null, ]; } public function retryFailedPayment(Subscription $subscription): array { // No payments to retry for activation keys return $this->syncSubscriptionStatus($subscription); } public function canModifySubscription(Subscription $subscription): bool { try { $details = $this->getSubscriptionDetails($subscription->provider_subscription_id); return ! $details['is_activated']; // Can only modify before activation } catch (\Exception $e) { return false; } } public function getCancellationTerms(Subscription $subscription): array { return [ 'immediate_cancellation' => true, 'refund_policy' => 'non_refundable', 'cancellation_effective' => 'immediately', 'billing_cycle_proration' => false, ]; } public function exportSubscriptionData(Subscription $subscription): array { return [ 'provider' => 'activation_key', 'provider_subscription_id' => $subscription->provider_subscription_id, 'data' => $subscription->provider_data, ]; } public function importSubscriptionData(User $user, array $subscriptionData): array { throw new \Exception('Import to activation keys not implemented'); } // Notification methods protected function notifyActivationKeyGenerated(User $user, Plan $plan, string $activationKey): void { $message = "🔑 ACTIVATION KEY GENERATED\n". "👤 User: {$user->name} ({$user->email})\n". "📋 Plan: {$plan->name}\n". '💰 Price: $'.number_format($plan->price, 2)."\n". "🏪 Provider: Activation Key\n". "🔑 Key: {$activationKey}\n". '📅 Generated: '.now()->format('Y-m-d H:i:s'); $this->sendTelegramNotification($message); } protected function notifyActivationKeyRedeemed(User $user, Plan $plan, string $activationKey): void { $message = "✅ ACTIVATION KEY REDEEMED\n". "👤 User: {$user->name} ({$user->email})\n". "📋 Plan: {$plan->name}\n". "🏪 Provider: Activation Key\n". "🔑 Key: {$activationKey}\n". '📅 Redeemed: '.now()->format('Y-m-d H:i:s'); $this->sendTelegramNotification($message); } protected function notifySubscriptionActivated(User $user, Plan $plan, Subscription $subscription): void { $message = "🎉 SUBSCRIPTION ACTIVATED\n". "👤 User: {$user->name} ({$user->email})\n". "📋 Plan: {$plan->name}\n". "🏪 Provider: Activation Key\n". "🔄 Subscription ID: {$subscription->id}\n". '📅 Activated: '.now()->format('Y-m-d H:i:s'); $this->sendTelegramNotification($message); } protected function notifyProviderError(string $operation, string $error, array $context = []): void { $contextStr = ''; if (! empty($context)) { $contextStr = '📝 Details: '.json_encode(array_slice($context, 0, 3, true), JSON_UNESCAPED_SLASHES)."\n"; if (count($context) > 3) { $contextStr .= '📝 Additional: '.(count($context) - 3).' more items'."\n"; } } $message = "🚨 PROVIDER ERROR\n". "🏪 Provider: Activation Key\n". "📡 Operation: {$operation}\n". "💥 Error: {$error}\n". $contextStr. '⏰ Time: '.now()->format('Y-m-d H:i:s'); $this->sendTelegramNotification($message); } protected function notifySubscriptionCancelled(Subscription $subscription, string $reason): void { $user = $subscription->user; $plan = $subscription->plan; $message = "❌ SUBSCRIPTION CANCELLED\n". "👤 User: {$user->name} ({$user->email})\n". "📋 Plan: {$plan->name}\n". "🏪 Provider: Activation Key\n". "💭 Reason: {$reason}\n". "🆔 Subscription ID: {$subscription->id}\n". ($subscription->ends_at ? '📅 Effective: '.$subscription->ends_at->format('Y-m-d')."\n" : ''). '⏰ Cancelled: '.now()->format('Y-m-d H:i:s'); $this->sendTelegramNotification($message); } }