feat(subscription): enhance subscription management with loading states and Stripe status checks

This commit is contained in:
Andras Bacsai
2025-04-14 10:31:13 +02:00
parent 8b3c4d7ad9
commit 32326bb7dd
5 changed files with 101 additions and 43 deletions

View File

@@ -12,19 +12,30 @@ class Index extends Component
public bool $alreadySubscribed = false;
public bool $isUnpaid = false;
public bool $isCancelled = false;
public bool $isMember = false;
public bool $loading = true;
public function mount()
{
if (! isCloud()) {
return redirect(RouteServiceProvider::HOME);
}
if (auth()->user()?->isMember()) {
return redirect()->route('dashboard');
$this->isMember = true;
}
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
return redirect()->route('subscription.show');
}
$this->settings = instanceSettings();
$this->alreadySubscribed = currentTeam()->subscription()->exists();
if (! $this->alreadySubscribed) {
$this->loading = false;
}
}
public function stripeCustomerPortal()
@@ -37,6 +48,33 @@ class Index extends Component
return redirect($session->url);
}
public function getStripeStatus()
{
$subscription = currentTeam()->subscription;
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
$customer = $stripe->customers->retrieve(currentTeam()->subscription->stripe_customer_id);
if ($customer) {
$subscriptions = $stripe->subscriptions->all(['customer' => $customer->id]);
$currentTeam = currentTeam()->id ?? null;
if (count($subscriptions->data) > 0 && $currentTeam) {
$foundSubscription = collect($subscriptions->data)->firstWhere('metadata.team_id', $currentTeam);
if ($foundSubscription) {
$status = data_get($foundSubscription, 'status');
$subscription->update([
'stripe_subscription_id' => $foundSubscription->id,
]);
if ($status === 'unpaid') {
$this->isUnpaid = true;
}
}
}
if (count($subscriptions->data) === 0) {
$this->isCancelled = true;
}
}
$this->loading = false;
}
public function render()
{
return view('livewire.subscription.index');

View File

@@ -192,8 +192,6 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
public function subscriptionEnded()
{
$this->subscription->update([
'stripe_subscription_id' => null,
'stripe_plan_id' => null,
'stripe_cancel_at_period_end' => false,
'stripe_invoice_paid' => false,
'stripe_trial_already_ended' => false,

View File

@@ -8,13 +8,8 @@
<h1>Dashboard</h1>
<div class="subtitle">Your self-hosted infrastructure.</div>
@if (request()->query->get('success'))
<div class="items-center justify-center mb-10 font-bold rounded alert alert-success">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
Your subscription has been activated! Welcome onboard! <br>It could take a few seconds before your
<div class=" mb-10 font-bold alert alert-success">
Your subscription has been activated! Welcome onboard! It could take a few seconds before your
subscription is activated.<br> Please be patient.
</div>
@endif

View File

@@ -3,37 +3,62 @@
Subscribe | Coolify
</x-slot>
@if (auth()->user()->isAdminFromSession())
<div>
<div class="flex gap-2">
<h1>Subscriptions</h1>
@if (subscriptionProvider() === 'stripe' && $alreadySubscribed)
<x-forms.button wire:click='stripeCustomerPortal'>Manage My Subscription</x-forms.button>
@endif
@if (request()->query->get('cancelled'))
<div class="mb-6 rounded alert-error">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>Something went wrong with your subscription. Please try again or contact
support.</span>
</div>
@if (request()->query->get('cancelled'))
<div class="mb-6 rounded alert-error">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>Something went wrong with your subscription. Please try again or contact
support.</span>
</div>
@endif
@if (config('subscription.provider') === 'stripe')
<livewire:subscription.pricing-plans />
@endif
@endif
<div class="flex gap-2">
<h1>Subscriptions</h1>
</div>
@if ($loading)
<div class="flex gap-2" wire:init="getStripeStatus">
Loading your subscription status...
</div>
@else
@if ($isUnpaid)
<div class="mb-6 rounded alert-error">
<span>Your last payment was failed for Coolify Cloud.</span>
</div>
<div>
<p class="mb-2">Open the following link, navigate to the button and pay your unpaid/past due
subscription.
</p>
<x-forms.button wire:click='stripeCustomerPortal'>Billing Portal</x-forms.button>
</div>
@else
@if (config('subscription.provider') === 'stripe')
<div @class([
'pb-4' => $isCancelled,
'pb-10' => !$isCancelled,
])>
@if ($isCancelled)
<div class="alert-error">
<span>It looks like your previous subscription has been cancelled, because you forgot to
pay
the bills.<br />Please subscribe again to continue using Coolify.</span>
</div>
@endif
</div>
<livewire:subscription.pricing-plans />
@endif
@endif
@endif
@else
<div class="flex flex-col justify-center mx-10">
<div class="flex gap-2">
<h1>Subscription</h1>
</div>
<div>You are not an admin or have been removed from this team. If this does not make sense, please <span
class="underline cursor-pointer dark:text-white" wire:click="help">contact
us</span>.</div>
<div>You are not an admin so you cannot manage your Team's subscription. If this does not make sense, please
<span class="underline cursor-pointer dark:text-white" wire:click="help">contact
us</span>.
</div>
</div>
@endif
</div>

View File

@@ -1,4 +1,4 @@
<div x-data="{ selected: 'monthly' }" class="w-full pb-20 pt-10">
<div x-data="{ selected: 'monthly' }" class="w-full pb-20">
<div class="px-6 mx-auto lg:px-8">
<div class="flex justify-center">
<fieldset
@@ -72,14 +72,16 @@
</div>
</div>
</div>
<x-forms.button x-show="selected === 'monthly'" x-cloak aria-describedby="tier-basic"
class="w-full h-10 buyme" wire:click="subscribeStripe('dynamic-monthly')">
Subscribe
</x-forms.button>
<x-forms.button x-show="selected === 'yearly'" x-cloak aria-describedby="tier-basic"
class="w-full h-10 buyme" wire:click="subscribeStripe('dynamic-yearly')">
Subscribe
</x-forms.button>
<div class="flex pt-4 h-14">
<x-forms.button x-show="selected === 'monthly'" x-cloak aria-describedby="tier-basic"
class="w-full" wire:click="subscribeStripe('dynamic-monthly')">
Subscribe
</x-forms.button>
<x-forms.button x-show="selected === 'yearly'" x-cloak aria-describedby="tier-basic"
class="w-full" wire:click="subscribeStripe('dynamic-yearly')">
Subscribe
</x-forms.button>
</div>
<ul role="list" class="mt-8 space-y-3 text-sm leading-6 dark:text-neutral-400">
<li class="flex">
<svg class="flex-none w-5 h-6 mr-3 text-warning" viewBox="0 0 20 20" fill="currentColor"