fix: stripe invoice paid webhook

fix: prepare customer initiated tier change
fix: separate view for subscriptions
This commit is contained in:
Andras Bacsai 2024-02-23 11:21:14 +01:00
parent ce09ef8848
commit b59e47dcf9
11 changed files with 108 additions and 59 deletions

View File

@ -7,6 +7,13 @@ use Livewire\Component;
class Actions extends Component class Actions extends Component
{ {
public $server_limits = 0;
public function mount()
{
$limits = currentTeam()->limits;
$this->server_limits = data_get($limits, 'serverLimit', 0);
}
public function cancel() public function cancel()
{ {
try { try {
@ -69,7 +76,8 @@ class Actions extends Component
return handleError($e, $this); return handleError($e, $this);
} }
} }
public function stripeCustomerPortal() { public function stripeCustomerPortal()
{
$session = getStripeCustomerPortalSession(currentTeam()); $session = getStripeCustomerPortalSession(currentTeam());
redirect($session->url); redirect($session->url);
} }

View File

@ -0,0 +1,19 @@
<?php
namespace App\Livewire\Subscription;
use Livewire\Component;
class Show extends Component
{
public function mount()
{
if (!isCloud()) {
return redirect()->route('dashboard');
}
}
public function render()
{
return view('livewire.subscription.show');
}
}

View File

@ -109,7 +109,7 @@ function isPaddle()
function getStripeCustomerPortalSession(Team $team) function getStripeCustomerPortalSession(Team $team)
{ {
Stripe::setApiKey(config('subscription.stripe_api_key')); Stripe::setApiKey(config('subscription.stripe_api_key'));
$return_url = route('team.index'); $return_url = route('subscription.show');
$stripe_customer_id = data_get($team,'subscription.stripe_customer_id'); $stripe_customer_id = data_get($team,'subscription.stripe_customer_id');
if (!$stripe_customer_id) { if (!$stripe_customer_id) {
return null; return null;
@ -123,7 +123,7 @@ function getStripeCustomerPortalSession(Team $team)
function allowedPathsForUnsubscribedAccounts() function allowedPathsForUnsubscribedAccounts()
{ {
return [ return [
'subscription', 'subscription/new',
'login', 'login',
'logout', 'logout',
'waitlist', 'waitlist',

View File

@ -130,11 +130,20 @@
<path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /> <path d="M5 5a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" />
<path d="M3 13v-1a2 2 0 0 1 2 -2h2" /> <path d="M3 13v-1a2 2 0 0 1 2 -2h2" />
</svg> </svg>
Teams @if (isCloud()) Teams
/ Subscription
@endif
</a> </a>
</li> </li>
@if (isCloud())
<li title="Subscription" class="hover:bg-coolgray-200">
<a class="hover:bg-transparent hover:no-underline"
href="{{ route('subscription.show') }}">
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('subscription*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v8a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3zm0 2h18M7 15h.01M11 15h2"/>
</svg>
Subscription
</a>
</li>
@endif
@if (isInstanceAdmin()) @if (isInstanceAdmin())
<li title="Settings" class="hover:bg-coolgray-200"> <li title="Settings" class="hover:bg-coolgray-200">

View File

@ -34,7 +34,7 @@
<div> <div>
</div> </div>
</div> </div>
<div class="p-4 rounded bg-coolgray-400"> {{-- <div class="p-4 rounded bg-coolgray-400">
<h2 id="tier-hobby" class="flex items-start gap-4 text-4xl font-bold tracking-tight">Unlimited Trial <h2 id="tier-hobby" class="flex items-start gap-4 text-4xl font-bold tracking-tight">Unlimited Trial
<x-forms.button><a class="font-bold text-white hover:no-underline" <x-forms.button><a class="font-bold text-white hover:no-underline"
href="https://github.com/coollabsio/coolify">Get Started</a></x-forms.button> href="https://github.com/coollabsio/coolify">Get Started</a></x-forms.button>
@ -42,8 +42,11 @@
<p class="mt-4 text-sm leading-6">Start self-hosting <span class="text-warning">without limits</span> with <p class="mt-4 text-sm leading-6">Start self-hosting <span class="text-warning">without limits</span> with
our our
OSS version. Same features as the paid version, but you have to manage by yourself.</p> OSS version. Same features as the paid version, but you have to manage by yourself.</p>
</div> </div> --}}
<div class="flow-root mt-12"> <div class="flow-root mt-12">
<div class="pb-10 text-xl text-center">For the detailed list of features, please visit our landing page: <a
class="font-bold text-white underline" href="https://coolify.io">coolify.io</a></div>
<div <div
class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap-y-16 sm:mx-auto lg:-mx-8 lg:mt-0 lg:max-w-none lg:grid-cols-3 lg:divide-x lg:divide-y-0 xl:-mx-4"> class="grid max-w-sm grid-cols-1 -mt-16 divide-y divide-coolgray-500 isolate gap-y-16 sm:mx-auto lg:-mx-8 lg:mt-0 lg:max-w-none lg:grid-cols-3 lg:divide-x lg:divide-y-0 xl:-mx-4">
@ -70,21 +73,18 @@
{{ $basic }} {{ $basic }}
@endisset @endisset
@endif @endif
<p class="mt-10 text-sm leading-6 text-white h-[6.5rem]">Start self-hosting in <p class="mt-10 text-sm leading-6 text-white h-[6.5rem]">Begin hosting your own services in the
the cloud cloud.
with a
single
server.
</p> </p>
<ul role="list" class="space-y-3 text-sm leading-6 "> <ul role="list" class="space-y-3 text-sm leading-6 ">
<li class="flex gap-x-3"> <li class="flex">
<svg class="flex-none w-5 h-6 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"
aria-hidden="true"> aria-hidden="true">
<path fill-rule="evenodd" <path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" /> clip-rule="evenodd" />
</svg> </svg>
2 servers <x-helper helper="Bring Your Own Server." /> Connect <span class="px-1 font-bold text-white">2</span> servers
</li> </li>
<li class="flex gap-x-3"> <li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor" <svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
@ -141,17 +141,18 @@
{{ $pro }} {{ $pro }}
@endisset @endisset
@endif @endif
<p class="h-20 mt-10 text-sm leading-6 text-white">Scale your business or self-hosting environment. <p class="h-20 mt-10 text-sm leading-6 text-white">Expand your business or set up your own hosting
environment.
</p> </p>
<ul role="list" class="mt-6 space-y-3 text-sm leading-6 "> <ul role="list" class="mt-6 space-y-3 text-sm leading-6 ">
<li class="flex gap-x-3"> <li class="flex ">
<svg class="flex-none w-5 h-6 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"
aria-hidden="true"> aria-hidden="true">
<path fill-rule="evenodd" <path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" /> clip-rule="evenodd" />
</svg> </svg>
10 servers <x-helper helper="Bring Your Own Server." /> Connect <span class="px-1 font-bold text-white">10</span> servers
</li> </li>
<li class="flex gap-x-3"> <li class="flex gap-x-3">
<svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor" <svg class="flex-none w-5 h-6 text-warning" viewBox="0 0 20 20" fill="currentColor"
@ -208,17 +209,17 @@
{{ $ultimate }} {{ $ultimate }}
@endisset @endisset
@endif @endif
<p class="h-20 mt-10 text-sm leading-6 text-white">Deploy complex infrastructures and <p class="h-20 mt-10 text-sm leading-6 text-white">Easily manage complex infrastructures in a
manage them easily in one place.</p> single location.</p>
<ul role="list" class="mt-6 space-y-3 text-sm leading-6 "> <ul role="list" class="mt-6 space-y-3 text-sm leading-6 ">
<li class="flex gap-x-3"> <li class="flex ">
<svg class="flex-none w-5 h-6 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"
aria-hidden="true"> aria-hidden="true">
<path fill-rule="evenodd" <path fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
clip-rule="evenodd" /> clip-rule="evenodd" />
</svg> </svg>
? servers <x-helper helper="Bring Your Own Server." /> Connect <span class="px-1 font-bold text-white">unlimited</span> servers
</li> </li>
<li class="flex gap-x-3"> <li class="flex gap-x-3">
@ -254,7 +255,7 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="p-4 mt-10 rounded"> {{-- <div class="p-4 mt-10 rounded">
<div class="flex items-start gap-4 text-xl tracking-tight">Need official support for <div class="flex items-start gap-4 text-xl tracking-tight">Need official support for
your self-hosted instance? your self-hosted instance?
<x-forms.button> <x-forms.button>
@ -263,9 +264,10 @@
Us</a> Us</a>
</x-forms.button> </x-forms.button>
</div> </div>
</div> </div> --}}
</div> </div>
<div class="pt-8 pb-12 text-4xl font-bold text-center text-white">Included in all plans</div>
{{-- <div class="pt-8 pb-12 text-4xl font-bold text-center text-white">Included in all plans</div>
<div class="grid grid-cols-1 gap-10 md:grid-cols-2 gap-y-28"> <div class="grid grid-cols-1 gap-10 md:grid-cols-2 gap-y-28">
<div> <div>
<div class="flex items-center gap-4 mb-4"> <div class="flex items-center gap-4 mb-4">
@ -433,7 +435,7 @@
</div> </div>
<div class="pt-20 text-xs"> <div class="pt-20 text-xs">
<span class="text-warning">*</span> Some features are work in progress and will be available soon. <span class="text-warning">*</span> Some features are work in progress and will be available soon.
</div> </div> --}}
</div> </div>
@isset($other) @isset($other)
{{ $other }} {{ $other }}

View File

@ -3,5 +3,5 @@ We would like to inform you that a {{ config('constants.limits.trial_period') }}
You can try out Coolify, without payment information for free. If you like it, you can upgrade to a paid plan at any time. You can try out Coolify, without payment information for free. If you like it, you can upgrade to a paid plan at any time.
[Click here](https://app.coolify.io/subscription) to start your trial. [Click here](https://app.coolify.io/subscription/new) to start your trial.
</x-emails.layout> </x-emails.layout>

View File

@ -1,25 +1,34 @@
<div> <div>
@if (subscriptionProvider() === 'stripe') @if (subscriptionProvider() === 'stripe')
<x-forms.button wire:click='stripeCustomerPortal'>Manage My Subscription</x-forms.button>
<div class="pt-4"> <div class="pt-4">
<div>Current Plan: <span class="text-warning">{{ data_get(currentTeam(), 'subscription')->type() }}<span> <div class="pb-4">Your current Plan is: <strong
</div> class="text-warning">{{ data_get(currentTeam(), 'subscription')->type() }}</strong></div>
@if (currentTeam()->subscription->stripe_cancel_at_period_end) @if (currentTeam()->subscription->stripe_cancel_at_period_end)
<div>Subscription is active but on cancel period.</div> <div>Subscription is active but on cancel period.</div>
@else @else
<div>Subscription is active. Last invoice is <div>Subscription is active. Last invoice is
{{ currentTeam()->subscription->stripe_invoice_paid ? 'paid' : 'not paid' }}.</div> {{ currentTeam()->subscription->stripe_invoice_paid ? 'paid' : 'not paid' }}.</div>
@endif @endif
<h3 class="pt-4">Limits</h3>
<div>Server: {{ $server_limits }}</div>
<h3 class="pt-4">Actions</h3>
<div class="pb-4">Cancel, upgrade or downgrade your subscription.</div>
<div class="flex gap-2">
<x-forms.button wire:click='stripeCustomerPortal'>Manage your subscription on <svg
xmlns="http://www.w3.org/2000/svg" class="w-12" viewBox="0 0 512 214">
<path fill="#635BFF"
d="M512 110.08c0-36.409-17.636-65.138-51.342-65.138c-33.85 0-54.33 28.73-54.33 64.854c0 42.808 24.179 64.426 58.88 64.426c16.925 0 29.725-3.84 39.396-9.244v-28.445c-9.67 4.836-20.764 7.823-34.844 7.823c-13.796 0-26.027-4.836-27.591-21.618h69.547c0-1.85.284-9.245.284-12.658Zm-70.258-13.511c0-16.071 9.814-22.756 18.774-22.756c8.675 0 17.92 6.685 17.92 22.756h-36.694Zm-90.31-51.627c-13.939 0-22.899 6.542-27.876 11.094l-1.85-8.818h-31.288v165.83l35.555-7.537l.143-40.249c5.12 3.698 12.657 8.96 25.173 8.96c25.458 0 48.64-20.48 48.64-65.564c-.142-41.245-23.609-63.716-48.498-63.716Zm-8.534 97.991c-8.391 0-13.37-2.986-16.782-6.684l-.143-52.765c3.698-4.124 8.818-6.968 16.925-6.968c12.942 0 21.902 14.506 21.902 33.137c0 19.058-8.818 33.28-21.902 33.28ZM241.493 36.551l35.698-7.68V0l-35.698 7.538V36.55Zm0 10.809h35.698v124.444h-35.698V47.36Zm-38.257 10.524L200.96 47.36h-30.72v124.444h35.556V87.467c8.39-10.951 22.613-8.96 27.022-7.396V47.36c-4.551-1.707-21.191-4.836-29.582 10.524Zm-71.112-41.386l-34.702 7.395l-.142 113.92c0 21.05 15.787 36.551 36.836 36.551c11.662 0 20.195-2.133 24.888-4.693V140.8c-4.55 1.849-27.022 8.391-27.022-12.658V77.653h27.022V47.36h-27.022l.142-30.862ZM35.982 83.484c0-5.546 4.551-7.68 12.09-7.68c10.808 0 24.461 3.272 35.27 9.103V51.484c-11.804-4.693-23.466-6.542-35.27-6.542C19.2 44.942 0 60.018 0 85.192c0 39.252 54.044 32.995 54.044 49.92c0 6.541-5.688 8.675-13.653 8.675c-11.804 0-26.88-4.836-38.827-11.378v33.849c13.227 5.689 26.596 8.106 38.827 8.106c29.582 0 49.92-14.648 49.92-40.106c-.142-42.382-54.329-34.845-54.329-50.774Z" />
</svg></x-forms.button>
@if (currentTeam()->subscription->stripe_cancel_at_period_end) </div>
<a class="hover:no-underline" href="{{ route('subscription.index') }}"><x-forms.button>Subscribe </div>
again</x-forms.button></a> <div class="pt-4">
@endif If you have any problem, please <a class="text-white underline" href="{{ config('coolify.contact') }}"
<div>To update your subscription (upgrade / downgrade), please <a class="text-white underline" target="_blank">contact us.</a>
href="{{ config('coolify.contact') }}" target="_blank">contact us.</a></div>
</div> </div>
@endif @endif
@if (subscriptionProvider() === 'lemon') {{-- @if (subscriptionProvider() === 'lemon')
<div>Status: {{ currentTeam()->subscription->lemon_status }}</div> <div>Status: {{ currentTeam()->subscription->lemon_status }}</div>
<div>Type: {{ currentTeam()->subscription->lemon_variant_name }}</div> <div>Type: {{ currentTeam()->subscription->lemon_variant_name }}</div>
@if (currentTeam()->subscription->lemon_status === 'cancelled') @if (currentTeam()->subscription->lemon_status === 'cancelled')
@ -49,6 +58,5 @@
Subscription</x-forms.button></a> Subscription</x-forms.button></a>
</div> </div>
</div> </div>
@endif @endif --}}
</div> </div>

View File

@ -0,0 +1,16 @@
<div>
<div >
<h1>Subscription</h1>
<div>Here you can see and manage your subscription.</div>
</div>
<div class="pb-8">
@if (data_get(currentTeam(), 'subscription'))
<livewire:subscription.actions />
@else
<div>You are not subscribed to any plan. Please subscribe to a plan to continue.</div>
<x-forms.button class="mt-4"><a class="text-white hover:no-underline"
href="{{ route('subscription.index') }}">Subscribe Now</a>
</x-forms.button>
@endif
</div>
</div>

View File

@ -14,19 +14,6 @@
</div> </div>
</form> </form>
@if (isCloud())
<div class="pb-8">
<h2>Subscription</h2>
@if (data_get(currentTeam(), 'subscription'))
<livewire:subscription.actions />
@else
<x-forms.button class="mt-4"><a class="text-white hover:no-underline"
href="{{ route('subscription.index') }}">Subscribe Now</a>
</x-forms.button>
@endif
</div>
@endif
<div> <div>
<h2>Danger Zone</h2> <h2>Danger Zone</h2>
<div class="pb-4">Woah. I hope you know what are you doing.</div> <div class="pb-4">Woah. I hope you know what are you doing.</div>
@ -36,7 +23,7 @@
@elseif(auth()->user()->teams()->get()->count() === 1) @elseif(auth()->user()->teams()->get()->count() === 1)
<div>You can't delete your last team.</div> <div>You can't delete your last team.</div>
@elseif(currentTeam()->subscription && currentTeam()->subscription?->lemon_status !== 'cancelled') @elseif(currentTeam()->subscription && currentTeam()->subscription?->lemon_status !== 'cancelled')
<div>Please cancel your subscription before delete this team (Manage My Subscription).</div> <div>Please cancel your subscription <a class="text-white underline" href="{{route('subscription.show')}}">here</a> before delete this team.</div>
@else @else
@if (currentTeam()->isEmpty()) @if (currentTeam()->isEmpty())
<div class="pb-4">This will delete your team. Beware! There is no coming back!</div> <div class="pb-4">This will delete your team. Beware! There is no coming back!</div>

View File

@ -72,6 +72,7 @@ use App\Livewire\Server\Proxy\Show as ProxyShow;
use App\Livewire\Server\Proxy\Logs as ProxyLogs; use App\Livewire\Server\Proxy\Logs as ProxyLogs;
use App\Livewire\Source\Github\Change as GitHubChange; use App\Livewire\Source\Github\Change as GitHubChange;
use App\Livewire\Subscription\Index as SubscriptionIndex; use App\Livewire\Subscription\Index as SubscriptionIndex;
use App\Livewire\Subscription\Show as SubscriptionShow;
use App\Livewire\Tags\Index as TagsIndex; use App\Livewire\Tags\Index as TagsIndex;
use App\Livewire\Tags\Show as TagsShow; use App\Livewire\Tags\Show as TagsShow;
@ -110,7 +111,8 @@ Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/', Dashboard::class)->name('dashboard'); Route::get('/', Dashboard::class)->name('dashboard');
Route::get('/boarding', BoardingIndex::class)->name('boarding'); Route::get('/boarding', BoardingIndex::class)->name('boarding');
Route::get('/subscription', SubscriptionIndex::class)->name('subscription.index'); Route::get('/subscription/new', SubscriptionIndex::class)->name('subscription.index');
Route::get('/subscription', SubscriptionShow::class)->name('subscription.show');
Route::get('/settings', SettingsIndex::class)->name('settings.index'); Route::get('/settings', SettingsIndex::class)->name('settings.index');
Route::get('/settings/license', SettingsLicense::class)->name('settings.license'); Route::get('/settings/license', SettingsLicense::class)->name('settings.license');

View File

@ -816,9 +816,7 @@ Route::post('/payments/stripe/events', function () {
Sleep::for(5)->seconds(); Sleep::for(5)->seconds();
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
} }
$subscription->update([ $subscription->update([
'stripe_plan_id' => $planId,
'stripe_invoice_paid' => true, 'stripe_invoice_paid' => true,
]); ]);
break; break;