feat(payment): implement comprehensive Polar subscription sync with proper date and cancellation handling

- Add Polar-specific date field mapping in PaymentOrchestrator (current_period_start, current_period_end, cancelled_at, trial_end)
  - Handle both cancellation scenarios: cancel_at_period_end=true and existing cancelled_at timestamp
  - Map customer_cancellation_reason and customer_cancellation_comment from Polar to database
  - Update billing page to show correct renewal vs expiry dates based on cancellation status
  - Restrict cancel button to activation_key provider only (Polar uses customer portal)
  - Fix button spacing between "Manage in Polar" and "Sync" buttons
  - Ensure both "Sync" and "Recheck Status" buttons use identical sync functionality
This commit is contained in:
idevakk
2025-12-06 10:42:25 -08:00
parent 0724e6da43
commit 15e018eb88
4 changed files with 150 additions and 18 deletions

View File

@@ -197,7 +197,7 @@ class Subscription extends Model
{
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)) {
if ($this->provider === 'polar' && empty($this->provider_subscription_id) && ! empty($this->user->polar_cust_id)) {
$this->fetchPolarSubscriptionId();
}
@@ -252,7 +252,7 @@ class Subscription extends Model
$data = $response->json();
$subscriptions = $data['items'] ?? [];
if (!empty($subscriptions)) {
if (! empty($subscriptions)) {
// Find the subscription that matches our plan or take the most recent active one
$matchingSubscription = null;
@@ -265,23 +265,55 @@ class Subscription extends Model
}
// If no exact match, take the most recent active subscription
if (!$matchingSubscription && !empty($subscriptions)) {
if (! $matchingSubscription && ! empty($subscriptions)) {
$matchingSubscription = $subscriptions[0];
}
if ($matchingSubscription) {
// Parse dates from Polar response
$startsAt = null;
$endsAt = null;
$cancelledAt = null;
// Handle current_period_start
if (isset($matchingSubscription['current_period_start'])) {
$startsAt = \Carbon\Carbon::parse($matchingSubscription['current_period_start']);
}
// Handle current_period_end (renewal date)
if (isset($matchingSubscription['current_period_end'])) {
$endsAt = \Carbon\Carbon::parse($matchingSubscription['current_period_end']);
}
// Handle ends_at (cancellation/expiry date)
elseif (isset($matchingSubscription['ends_at'])) {
$endsAt = \Carbon\Carbon::parse($matchingSubscription['ends_at']);
}
// Handle expires_at (expiry date)
elseif (isset($matchingSubscription['expires_at'])) {
$endsAt = \Carbon\Carbon::parse($matchingSubscription['expires_at']);
}
// Handle cancelled_at
if (isset($matchingSubscription['cancelled_at'])) {
$cancelledAt = \Carbon\Carbon::parse($matchingSubscription['cancelled_at']);
}
$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,
'starts_at' => $startsAt,
'ends_at' => $endsAt,
'cancelled_at' => $cancelledAt,
'provider_data' => array_merge($this->provider_data ?? [], [
'polar_subscription' => $matchingSubscription,
'subscription_id_fetched_at' => now()->toISOString(),
'polar_dates' => [
'current_period_start' => $matchingSubscription['current_period_start'] ?? null,
'current_period_end' => $matchingSubscription['current_period_end'] ?? null,
'ends_at' => $matchingSubscription['ends_at'] ?? null,
'expires_at' => $matchingSubscription['expires_at'] ?? null,
'cancelled_at' => $matchingSubscription['cancelled_at'] ?? null,
],
]),
]);
@@ -289,6 +321,9 @@ class Subscription extends Model
'subscription_id' => $this->id,
'polar_subscription_id' => $matchingSubscription['id'],
'customer_id' => $this->user->polar_cust_id,
'starts_at' => $startsAt?->toISOString(),
'ends_at' => $endsAt?->toISOString(),
'cancelled_at' => $cancelledAt?->toISOString(),
]);
}
}