diff --git a/app/Models/Subscription.php b/app/Models/Subscription.php index be2d069..086529f 100644 --- a/app/Models/Subscription.php +++ b/app/Models/Subscription.php @@ -48,6 +48,7 @@ class Subscription extends Model 'polar_order', 'order_created_at', 'order_paid_at', + 'provider_checkout_id', ]; protected $casts = [ @@ -195,6 +196,11 @@ class Subscription extends Model public function syncWithProvider(): bool { try { + // For Polar provider, check if we need to fetch subscription ID first + if ($this->provider === 'polar' && empty($this->provider_subscription_id) && !empty($this->user->polar_cust_id)) { + $this->fetchPolarSubscriptionId(); + } + $orchestrator = app(PaymentOrchestrator::class); $result = $orchestrator->syncSubscriptionStatus($this); @@ -223,6 +229,86 @@ class Subscription extends Model } } + /** + * Fetch Polar subscription ID using customer ID if missing + */ + protected function fetchPolarSubscriptionId(): void + { + if ($this->provider !== 'polar' || empty($this->user->polar_cust_id)) { + return; + } + + try { + $polarProvider = app(\App\Services\Payments\Providers\PolarProvider::class); + + // Get active subscriptions for this customer + $response = $polarProvider->makeAuthenticatedRequest('GET', '/subscriptions', [ + 'customer_id' => $this->user->polar_cust_id, + 'status' => 'active', + 'limit' => 10, + ]); + + if ($response->successful()) { + $data = $response->json(); + $subscriptions = $data['items'] ?? []; + + if (!empty($subscriptions)) { + // Find the subscription that matches our plan or take the most recent active one + $matchingSubscription = null; + + foreach ($subscriptions as $sub) { + // Check if this subscription matches our plan (via metadata or other criteria) + if (isset($sub['metadata']['plan_id']) && $sub['metadata']['plan_id'] == $this->plan_id) { + $matchingSubscription = $sub; + break; + } + } + + // If no exact match, take the most recent active subscription + if (!$matchingSubscription && !empty($subscriptions)) { + $matchingSubscription = $subscriptions[0]; + } + + if ($matchingSubscription) { + $this->update([ + 'provider_subscription_id' => $matchingSubscription['id'], + 'status' => $matchingSubscription['status'], + 'starts_at' => isset($matchingSubscription['current_period_start']) + ? \Carbon\Carbon::parse($matchingSubscription['current_period_start']) + : null, + 'ends_at' => isset($matchingSubscription['current_period_end']) + ? \Carbon\Carbon::parse($matchingSubscription['current_period_end']) + : null, + 'provider_data' => array_merge($this->provider_data ?? [], [ + 'polar_subscription' => $matchingSubscription, + 'subscription_id_fetched_at' => now()->toISOString(), + ]), + ]); + + Log::info('Polar subscription ID fetched and updated', [ + 'subscription_id' => $this->id, + 'polar_subscription_id' => $matchingSubscription['id'], + 'customer_id' => $this->user->polar_cust_id, + ]); + } + } + } else { + Log::warning('Failed to fetch Polar subscriptions for sync', [ + 'customer_id' => $this->user->polar_cust_id, + 'status_code' => $response->status(), + 'response' => $response->json(), + ]); + } + + } catch (\Exception $e) { + Log::error('Error fetching Polar subscription ID', [ + 'subscription_id' => $this->id, + 'customer_id' => $this->user->polar_cust_id ?? 'none', + 'error' => $e->getMessage(), + ]); + } + } + /** * Cancel the subscription */ diff --git a/app/Services/Payments/Providers/PolarProvider.php b/app/Services/Payments/Providers/PolarProvider.php index 116fd92..5bac3bf 100644 --- a/app/Services/Payments/Providers/PolarProvider.php +++ b/app/Services/Payments/Providers/PolarProvider.php @@ -132,7 +132,7 @@ class PolarProvider implements PaymentProviderContract } } - protected function makeAuthenticatedRequest(string $method, string $endpoint, array $data = []): \Illuminate\Http\Client\Response + public function makeAuthenticatedRequest(string $method, string $endpoint, array $data = []): \Illuminate\Http\Client\Response { $url = $this->apiBaseUrl.$endpoint; @@ -261,7 +261,8 @@ class PolarProvider implements PaymentProviderContract 'stripe_id' => $checkout['id'], // Using stripe_id field for Polar checkout ID 'stripe_status' => 'pending', 'provider' => $this->getName(), - 'provider_subscription_id' => $checkout['id'], + 'provider_checkout_id' => $checkout['id'], // Store checkout ID separately + 'provider_subscription_id' => null, // Will be populated via webhook or sync 'status' => 'pending_payment', 'starts_at' => null, 'ends_at' => null, @@ -1170,9 +1171,9 @@ class PolarProvider implements PaymentProviderContract { $polarSubscription = $webhookData['data']['object']; - // Find and update local subscription + // Find and update local subscription using checkout_id $localSubscription = Subscription::where('provider', 'polar') - ->where('provider_subscription_id', $polarSubscription['checkout_id']) + ->where('provider_checkout_id', $polarSubscription['checkout_id']) ->first(); if ($localSubscription) { diff --git a/database/migrations/2025_12_06_101452_add_provider_checkout_id_to_subscriptions_table.php b/database/migrations/2025_12_06_101452_add_provider_checkout_id_to_subscriptions_table.php new file mode 100644 index 0000000..39cdf3b --- /dev/null +++ b/database/migrations/2025_12_06_101452_add_provider_checkout_id_to_subscriptions_table.php @@ -0,0 +1,29 @@ +string('provider_checkout_id')->nullable()->after('provider_subscription_id'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('subscriptions', function (Blueprint $table) { + $table->dropColumn('provider_checkout_id'); + }); + } +};