diff --git a/app/Console/Commands/CloudCleanupSubscriptions.php b/app/Console/Commands/CloudCleanupSubscriptions.php index 9dc6e24f5..ab676c927 100644 --- a/app/Console/Commands/CloudCleanupSubscriptions.php +++ b/app/Console/Commands/CloudCleanupSubscriptions.php @@ -50,7 +50,7 @@ class CloudCleanupSubscriptions extends Command } else { $subscription = $stripe->subscriptions->retrieve(data_get($team, 'subscription.stripe_subscription_id'), []); $status = data_get($subscription, 'status'); - if ($status === 'active' || $status === 'past_due') { + if ($status === 'active') { $team->subscription->update([ 'stripe_invoice_paid' => true, 'stripe_trial_already_ended' => false, diff --git a/app/Jobs/StripeProcessJob.php b/app/Jobs/StripeProcessJob.php index 803029772..f1c5bc1a8 100644 --- a/app/Jobs/StripeProcessJob.php +++ b/app/Jobs/StripeProcessJob.php @@ -73,19 +73,21 @@ class StripeProcessJob implements ShouldQueue } $subscription = Subscription::where('team_id', $teamId)->first(); if ($subscription) { - send_internal_notification('Old subscription activated for team: '.$teamId); + // send_internal_notification('Old subscription activated for team: '.$teamId); $subscription->update([ 'stripe_subscription_id' => $subscriptionId, 'stripe_customer_id' => $customerId, 'stripe_invoice_paid' => true, + 'stripe_past_due' => false, ]); } else { - send_internal_notification('New subscription for team: '.$teamId); + // send_internal_notification('New subscription for team: '.$teamId); Subscription::create([ 'team_id' => $teamId, 'stripe_subscription_id' => $subscriptionId, 'stripe_customer_id' => $customerId, 'stripe_invoice_paid' => true, + 'stripe_past_due' => false, ]); } break; @@ -100,6 +102,7 @@ class StripeProcessJob implements ShouldQueue if ($subscription) { $subscription->update([ 'stripe_invoice_paid' => true, + 'stripe_past_due' => false, ]); } else { throw new \RuntimeException("No subscription found for customer: {$customerId}"); @@ -119,9 +122,7 @@ class StripeProcessJob implements ShouldQueue } if (! $subscription->stripe_invoice_paid) { SubscriptionInvoiceFailedJob::dispatch($team); - send_internal_notification('Invoice payment failed: '.$customerId); - } else { - send_internal_notification('Invoice payment failed but already paid: '.$customerId); + // send_internal_notification('Invoice payment failed: '.$customerId); } break; case 'payment_intent.payment_failed': @@ -136,7 +137,7 @@ class StripeProcessJob implements ShouldQueue return; } - send_internal_notification('Subscription payment failed for customer: '.$customerId); + // send_internal_notification('Subscription payment failed for customer: '.$customerId); break; case 'customer.subscription.created': $customerId = data_get($data, 'customer'); @@ -158,7 +159,7 @@ class StripeProcessJob implements ShouldQueue } $subscription = Subscription::where('team_id', $teamId)->first(); if ($subscription) { - send_internal_notification("Subscription already exists for team: {$teamId}"); + // send_internal_notification("Subscription already exists for team: {$teamId}"); throw new \RuntimeException("Subscription already exists for team: {$teamId}"); } else { Subscription::create([ @@ -182,7 +183,7 @@ class StripeProcessJob implements ShouldQueue $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); if (! $subscription) { if ($status === 'incomplete_expired') { - send_internal_notification('Subscription incomplete expired'); + // send_internal_notification('Subscription incomplete expired'); throw new \RuntimeException('Subscription incomplete expired'); } if ($teamId) { @@ -217,16 +218,40 @@ class StripeProcessJob implements ShouldQueue 'stripe_plan_id' => $planId, 'stripe_cancel_at_period_end' => $cancelAtPeriodEnd, ]); - if ($status === 'paused' || $status === 'incomplete_expired' || $status === 'past_due') { + if ($status === 'paused' || $status === 'incomplete_expired') { if ($subscription->stripe_subscription_id === $subscriptionId) { $subscription->update([ 'stripe_invoice_paid' => false, ]); } } + if ($status === 'past_due') { + if ($subscription->stripe_subscription_id === $subscriptionId) { + $subscription->update([ + 'stripe_past_due' => true, + ]); + send_internal_notification('Past Due: '.$customerId.'Subscription ID: '.$subscriptionId); + } + } + if ($status === 'unpaid') { + if ($subscription->stripe_subscription_id === $subscriptionId) { + $subscription->update([ + 'stripe_invoice_paid' => false, + ]); + send_internal_notification('Unpaid: '.$customerId.'Subscription ID: '.$subscriptionId); + } + $team = data_get($subscription, 'team'); + if ($team) { + $team->subscriptionEnded(); + } else { + send_internal_notification('Subscription unpaid but no team found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No team found in Coolify for customer: {$customerId}"); + } + } if ($status === 'active') { if ($subscription->stripe_subscription_id === $subscriptionId) { $subscription->update([ + 'stripe_past_due' => false, 'stripe_invoice_paid' => true, ]); } diff --git a/app/Models/Team.php b/app/Models/Team.php index 467cf45bc..6796b22ad 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -93,6 +93,15 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen return $servers >= $serverLimit; } + public function subscriptionPastOverDue() + { + if (isCloud()) { + return $this->subscription?->stripe_past_due; + } + + return false; + } + public function serverOverflow() { if ($this->serverLimit() < $this->servers->count()) { @@ -185,6 +194,7 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen 'stripe_cancel_at_period_end' => false, 'stripe_invoice_paid' => false, 'stripe_trial_already_ended' => false, + 'stripe_past_due' => false, ]); foreach ($this->servers as $server) { $server->settings()->update([ diff --git a/config/constants.php b/config/constants.php index e7505de2e..8a515fa13 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.397', + 'version' => '4.0.0-beta.398', 'helper_version' => '1.0.7', 'realtime_version' => '1.0.6', 'self_hosted' => env('SELF_HOSTED', true), diff --git a/database/migrations/2025_03_01_112617_add_stripe_past_due.php b/database/migrations/2025_03_01_112617_add_stripe_past_due.php new file mode 100644 index 000000000..6edb4f698 --- /dev/null +++ b/database/migrations/2025_03_01_112617_add_stripe_past_due.php @@ -0,0 +1,28 @@ +boolean('stripe_past_due')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('subscriptions', function (Blueprint $table) { + $table->dropColumn('stripe_past_due'); + }); + } +}; diff --git a/resources/views/livewire/layout-popups.blade.php b/resources/views/livewire/layout-popups.blade.php index 53c8ab1d6..c639b4627 100644 --- a/resources/views/livewire/layout-popups.blade.php +++ b/resources/views/livewire/layout-popups.blade.php @@ -79,6 +79,16 @@ + @if (currentTeam()->subscriptionPastOverDue()) + +
WARNING: Your subscription is in over-due. If your latest + payment is not paid within a week, all automations will + be deactivated. Visit /subscription to check your subscription status or pay your + invoice (or check your email for the invoice). +
+
+ @endif @if (currentTeam()->serverOverflow())
WARNING: The number of active servers exceeds the limit diff --git a/versions.json b/versions.json index 8d1c91433..c9fb62130 100644 --- a/versions.json +++ b/versions.json @@ -1,10 +1,10 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.397" + "version": "4.0.0-beta.398" }, "nightly": { - "version": "4.0.0-beta.398" + "version": "4.0.0-beta.399" }, "helper": { "version": "1.0.7"