Files
zemailnator/app/Services/Payments/Providers/ActivationKeyProvider.php

498 lines
16 KiB
PHP

<?php
namespace App\Services\Payments\Providers;
use App\Contracts\Payments\PaymentProviderContract;
use App\Models\ActivationKey;
use App\Models\Plan;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class ActivationKeyProvider implements PaymentProviderContract
{
protected array $config;
public function __construct(array $config = [])
{
$this->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();
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,
]);
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();
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');
}
}