feat(subscription): enhance subscription management with loading states and Stripe status checks
This commit is contained in:
@@ -12,19 +12,30 @@ class Index extends Component
|
|||||||
|
|
||||||
public bool $alreadySubscribed = false;
|
public bool $alreadySubscribed = false;
|
||||||
|
|
||||||
|
public bool $isUnpaid = false;
|
||||||
|
|
||||||
|
public bool $isCancelled = false;
|
||||||
|
|
||||||
|
public bool $isMember = false;
|
||||||
|
|
||||||
|
public bool $loading = true;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (! isCloud()) {
|
if (! isCloud()) {
|
||||||
return redirect(RouteServiceProvider::HOME);
|
return redirect(RouteServiceProvider::HOME);
|
||||||
}
|
}
|
||||||
if (auth()->user()?->isMember()) {
|
if (auth()->user()?->isMember()) {
|
||||||
return redirect()->route('dashboard');
|
$this->isMember = true;
|
||||||
}
|
}
|
||||||
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
|
if (data_get(currentTeam(), 'subscription') && isSubscriptionActive()) {
|
||||||
return redirect()->route('subscription.show');
|
return redirect()->route('subscription.show');
|
||||||
}
|
}
|
||||||
$this->settings = instanceSettings();
|
$this->settings = instanceSettings();
|
||||||
$this->alreadySubscribed = currentTeam()->subscription()->exists();
|
$this->alreadySubscribed = currentTeam()->subscription()->exists();
|
||||||
|
if (! $this->alreadySubscribed) {
|
||||||
|
$this->loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function stripeCustomerPortal()
|
public function stripeCustomerPortal()
|
||||||
@@ -37,6 +48,33 @@ class Index extends Component
|
|||||||
return redirect($session->url);
|
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()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.subscription.index');
|
return view('livewire.subscription.index');
|
||||||
|
@@ -192,8 +192,6 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
|
|||||||
public function subscriptionEnded()
|
public function subscriptionEnded()
|
||||||
{
|
{
|
||||||
$this->subscription->update([
|
$this->subscription->update([
|
||||||
'stripe_subscription_id' => null,
|
|
||||||
'stripe_plan_id' => null,
|
|
||||||
'stripe_cancel_at_period_end' => false,
|
'stripe_cancel_at_period_end' => false,
|
||||||
'stripe_invoice_paid' => false,
|
'stripe_invoice_paid' => false,
|
||||||
'stripe_trial_already_ended' => false,
|
'stripe_trial_already_ended' => false,
|
||||||
|
@@ -8,13 +8,8 @@
|
|||||||
<h1>Dashboard</h1>
|
<h1>Dashboard</h1>
|
||||||
<div class="subtitle">Your self-hosted infrastructure.</div>
|
<div class="subtitle">Your self-hosted infrastructure.</div>
|
||||||
@if (request()->query->get('success'))
|
@if (request()->query->get('success'))
|
||||||
<div class="items-center justify-center mb-10 font-bold rounded alert alert-success">
|
<div class=" mb-10 font-bold alert alert-success">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
|
Your subscription has been activated! Welcome onboard! It could take a few seconds before your
|
||||||
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
|
|
||||||
subscription is activated.<br> Please be patient.
|
subscription is activated.<br> Please be patient.
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@@ -3,13 +3,6 @@
|
|||||||
Subscribe | Coolify
|
Subscribe | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
@if (auth()->user()->isAdminFromSession())
|
@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
|
|
||||||
</div>
|
|
||||||
@if (request()->query->get('cancelled'))
|
@if (request()->query->get('cancelled'))
|
||||||
<div class="mb-6 rounded alert-error">
|
<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"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
|
||||||
@@ -21,19 +14,51 @@
|
|||||||
support.</span>
|
support.</span>
|
||||||
</div>
|
</div>
|
||||||
@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')
|
@if (config('subscription.provider') === 'stripe')
|
||||||
<livewire:subscription.pricing-plans />
|
<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
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
<livewire:subscription.pricing-plans />
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
@else
|
@else
|
||||||
<div class="flex flex-col justify-center mx-10">
|
<div class="flex flex-col justify-center mx-10">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<h1>Subscription</h1>
|
<h1>Subscription</h1>
|
||||||
</div>
|
</div>
|
||||||
<div>You are not an admin or have been removed from this team. If this does not make sense, please <span
|
<div>You are not an admin so you cannot manage your Team's subscription. If this does not make sense, please
|
||||||
class="underline cursor-pointer dark:text-white" wire:click="help">contact
|
<span class="underline cursor-pointer dark:text-white" wire:click="help">contact
|
||||||
us</span>.</div>
|
us</span>.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
@@ -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="px-6 mx-auto lg:px-8">
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<fieldset
|
<fieldset
|
||||||
@@ -72,14 +72,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex pt-4 h-14">
|
||||||
<x-forms.button x-show="selected === 'monthly'" x-cloak aria-describedby="tier-basic"
|
<x-forms.button x-show="selected === 'monthly'" x-cloak aria-describedby="tier-basic"
|
||||||
class="w-full h-10 buyme" wire:click="subscribeStripe('dynamic-monthly')">
|
class="w-full" wire:click="subscribeStripe('dynamic-monthly')">
|
||||||
Subscribe
|
Subscribe
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
<x-forms.button x-show="selected === 'yearly'" x-cloak aria-describedby="tier-basic"
|
<x-forms.button x-show="selected === 'yearly'" x-cloak aria-describedby="tier-basic"
|
||||||
class="w-full h-10 buyme" wire:click="subscribeStripe('dynamic-yearly')">
|
class="w-full" wire:click="subscribeStripe('dynamic-yearly')">
|
||||||
Subscribe
|
Subscribe
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
|
</div>
|
||||||
<ul role="list" class="mt-8 space-y-3 text-sm leading-6 dark:text-neutral-400">
|
<ul role="list" class="mt-8 space-y-3 text-sm leading-6 dark:text-neutral-400">
|
||||||
<li class="flex">
|
<li class="flex">
|
||||||
<svg class="flex-none w-5 h-6 mr-3 text-warning" viewBox="0 0 20 20" fill="currentColor"
|
<svg class="flex-none w-5 h-6 mr-3 text-warning" viewBox="0 0 20 20" fill="currentColor"
|
||||||
|
Reference in New Issue
Block a user