fix: subscriptions

This commit is contained in:
Andras Bacsai 2023-08-30 18:23:55 +02:00
parent 5b6667c461
commit 923af88336
20 changed files with 147 additions and 69 deletions

View File

@ -12,7 +12,7 @@ class ServerController extends Controller
public function new_server()
{
if (!is_cloud() || isInstanceAdmin()) {
if (!is_cloud()) {
return view('server.create', [
'limit_reached' => false,
'private_keys' => PrivateKey::ownedByCurrentTeam()->get(),

View File

@ -48,8 +48,8 @@ public function subscribeStripe($type)
'enabled' => true,
],
'mode' => 'subscription',
'success_url' => route('subscription.success'),
'cancel_url' => route('subscription.index',['cancelled' => true]),
'success_url' => route('dashboard', ['success' => true]),
'cancel_url' => route('subscription.index', ['cancelled' => true]),
];
$customer = currentTeam()->subscription?->stripe_customer_id ?? null;
if ($customer) {

View File

@ -37,7 +37,7 @@ public function handle(): void
private function cleanup_waitlist()
{
$waitlist = Waitlist::whereVerified(false)->where('created_at', '<', now()->subMinutes(config('constants.waitlist.confirmation_valid_for_minutes')))->get();
$waitlist = Waitlist::whereVerified(false)->where('created_at', '<', now()->subMinutes(config('constants.waitlist.expiration')))->get();
foreach ($waitlist as $item) {
$item->delete();
}

View File

@ -9,7 +9,6 @@
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Stripe\Stripe;
class SubscriptionInvoiceFailedJob implements ShouldQueue
{

View File

@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Subscription extends Model
{
@ -14,19 +15,44 @@ public function team()
}
public function type()
{
$basic = explode(',', config('subscription.lemon_squeezy_basic_plan_ids'));
$pro = explode(',', config('subscription.lemon_squeezy_pro_plan_ids'));
$ultimate = explode(',', config('subscription.lemon_squeezy_ultimate_plan_ids'));
if (isLemon()) {
$basic = explode(',', config('subscription.lemon_squeezy_basic_plan_ids'));
$pro = explode(',', config('subscription.lemon_squeezy_pro_plan_ids'));
$ultimate = explode(',', config('subscription.lemon_squeezy_ultimate_plan_ids'));
$subscription = $this->lemon_variant_id;
if (in_array($subscription, $basic)) {
return 'basic';
$subscription = $this->lemon_variant_id;
if (in_array($subscription, $basic)) {
return 'basic';
}
if (in_array($subscription, $pro)) {
return 'pro';
}
if (in_array($subscription, $ultimate)) {
return 'ultimate';
}
}
if (in_array($subscription, $pro)) {
return 'pro';
}
if (in_array($subscription, $ultimate)) {
return 'ultimate';
if (isStripe()) {
if (!$this->stripe_plan_id) {
return 'unknown';
}
$subscription = Subscription::where('id', $this->id)->first();
if (!$subscription) {
return null;
}
$subscriptionPlanId = data_get($subscription,'stripe_plan_id');
if (!$subscriptionPlanId) {
return null;
}
$subscriptionConfigs = collect(config('subscription'));
$stripePlanId = null;
$subscriptionConfigs->map(function ($value, $key) use ($subscriptionPlanId, &$stripePlanId) {
if ($value === $subscriptionPlanId){
$stripePlanId = $key;
};
})->first();
if ($stripePlanId) {
return Str::of($stripePlanId)->after('stripe_price_id_')->before('_')->lower();
}
}
return 'unknown';
}

View File

@ -32,7 +32,7 @@ protected static function boot()
$team = [
'name' => $user->name . "'s Team",
'personal_team' => true,
'boarding' => true
'show_boarding' => true
];
if ($user->id === 0) {
$team['id'] = 0;
@ -94,7 +94,7 @@ public function isInstanceAdmin()
public function currentTeam()
{
return session('currentTeam');
return Team::find(session('currentTeam')->id);
}
public function otherTeams()

View File

@ -56,16 +56,16 @@ function isSubscriptionActive()
if (!$subscription) {
return false;
}
if (config('subscription.provider') === 'lemon') {
if (isLemon()) {
return $subscription->lemon_status === 'active';
}
if (config('subscription.provider') === 'stripe') {
// if (isPaddle()) {
// return $subscription->paddle_status === 'active';
// }
if (isStripe()) {
return $subscription->stripe_invoice_paid === true && $subscription->stripe_cancel_at_period_end === false;
}
return false;
// if (config('subscription.provider') === 'paddle') {
// return $subscription->paddle_status === 'active';
// }
}
function isSubscriptionOnGracePeriod()
@ -78,12 +78,12 @@ function isSubscriptionOnGracePeriod()
if (!$subscription) {
return false;
}
if (config('subscription.provider') === 'lemon') {
if (isLemon()) {
$is_still_grace_period = $subscription->lemon_ends_at &&
Carbon::parse($subscription->lemon_ends_at) > Carbon::now();
return $is_still_grace_period;
}
if (config('subscription.provider') === 'stripe') {
if (isStripe()) {
return $subscription->stripe_cancel_at_period_end;
}
return false;
@ -92,6 +92,15 @@ function subscriptionProvider()
{
return config('subscription.provider');
}
function isLemon () {
return config('subscription.provider') === 'lemon';
}
function isStripe() {
return config('subscription.provider') === 'stripe';
}
function isPaddle() {
return config('subscription.provider') === 'paddle';
}
function getStripeCustomerPortalSession(Team $team)
{
Stripe::setApiKey(config('subscription.stripe_api_key'));
@ -124,5 +133,6 @@ function allowedPathsForBoardingAccounts()
...allowedPathsForUnsubscribedAccounts(),
'boarding',
'livewire/message/boarding',
'livewire/message/boarding.index',
];
}

View File

@ -1,7 +1,7 @@
<?php
return [
'waitlist' => [
'confirmation_valid_for_minutes' => 10,
'expiration' => 10,
],
'invitation' => [
'link' => [
@ -15,5 +15,10 @@
'pro' => 10,
'ultimate' => 25,
],
'smtp' => [
'basic' => false,
'pro' => true,
'ultimate' => true,
],
],
];

View File

@ -0,0 +1,29 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('subscriptions', function (Blueprint $table) {
$table->string('stripe_plan_id')->nullable()->after('stripe_cancel_at_period_end');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('subscriptions', function (Blueprint $table) {
$table->dropColumn('stripe_plan_id');
});
}
};

View File

@ -12,7 +12,9 @@
@if ($attributes->get('type') === 'submit')
<span wire:target="submit" wire:loading.delay class="loading loading-xs text-warning loading-spinner"></span>
@else
<span wire:target="{{ explode('(', $attributes->whereStartsWith('wire:click')->first())[0] }}" wire:loading.delay
class="loading loading-xs loading-spinner"></span>
@if ($attributes->has('wire:click'))
<span wire:target="{{ $attributes->get('wire:click') }}" wire:loading.delay
class="loading loading-xs loading-spinner"></span>
@endif
@endif
</button>

View File

@ -4,20 +4,10 @@
<ol class="inline-flex items-center">
<li>
<div class="flex items-center">
<span>Currently active team: {{ session('currentTeam.name') }}</span>
<span>Currently active team: <span
class="text-warning">{{ session('currentTeam.name') }}</span></span>
</div>
</li>
@if (session('currentTeam.description'))
<li class="inline-flex items-center">
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold text-warning" fill="currentColor"
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
</svg>
<span class="truncate">{{ Str::limit(session('currentTeam.description'), 52) }}</span>
</li>
@endif
</ol>
</nav>
<nav class="navbar-main">

View File

@ -1,4 +1,4 @@
Someone added this email to the Coolify Cloud's waitlist.
<br>
<a href="{{ $confirmation_url }}">Click here to confirm</a>! The link will expire in {{config('constants.waitlist.confirmation_valid_for_minutes')}} minutes.<br><br>
<a href="{{ $confirmation_url }}">Click here to confirm</a>! The link will expire in {{config('constants.waitlist.expiration')}} minutes.<br><br>
You have no idea what <a href="https://coolify.io">Coolify Cloud</a> is or this waitlist? <a href="{{ $cancel_url }}">Click here to remove</a> you from the waitlist.

View File

@ -1,9 +1,19 @@
<div>
@if (session('error'))
<span x-data x-init="$wire.emit('error', '{{ session('error') }}')"/>
<span x-data x-init="$wire.emit('error', '{{ session('error') }}')" />
@endif
<h1>Dashboard</h1>
<div class="subtitle">Something <x-highlighted text="(more)" /> useful will be here.</div>
@if (request()->query->get('success'))
<div class="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>
<span>Your subscription has been activated! Welcome onboard!</span>
</div>
@endif
<div class="w-full rounded stats stats-vertical lg:stats-horizontal">
<div class="stat">
<div class="stat-title">Servers</div>
@ -25,4 +35,5 @@
<div class="stat-value">{{ $s3s }}</div>
</div>
</div>
</div>

View File

@ -1,17 +1,23 @@
<div>
@if (subscriptionProvider() === 'stripe')
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
<div>Subscription is active but on cancel period.</div>
@else
<div>Subscription is active. Last invoice is
{{ currentTeam()->subscription->stripe_invoice_paid ? 'paid' : 'not paid' }}.</div>
@endif
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
<a class="hover:no-underline" href="{{ route('subscription.index') }}"><x-forms.button>Subscribe
again</x-forms.button></a>
@endif
<x-forms.button wire:click='stripeCustomerPortal'>Manage My Subscription</x-forms.button>
<div class="pt-4">
<div>Current Plan: <span class="text-warning">{{ data_get(currentTeam(), 'subscription')->type() }}<span>
</div>
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
<div>Subscription is active but on cancel period.</div>
@else
<div>Subscription is active. Last invoice is
{{ currentTeam()->subscription->stripe_invoice_paid ? 'paid' : 'not paid' }}.</div>
@endif
@if (currentTeam()->subscription->stripe_cancel_at_period_end)
<a class="hover:no-underline" href="{{ route('subscription.index') }}"><x-forms.button>Subscribe
again</x-forms.button></a>
@endif
<div>To update your subscription (upgrade / downgrade), please <a class="text-white underline"
href="https://docs.coollabs.io/contact" target="_blank">contact us.</a></div>
</div>
@endif
@if (subscriptionProvider() === 'lemon')
<div>Status: {{ currentTeam()->subscription->lemon_status }}</div>

View File

@ -13,7 +13,7 @@
<div>You can't delete your last team.</div>
@elseif(currentTeam()->subscription &&
currentTeam()->subscription?->lemon_status !== 'cancelled')
<div>Please cancel your subscription before delete this team (Manage My Subscription button).</div>
<div>Please cancel your subscription before delete this team (Manage My Subscription).</div>
@else
@if (currentTeam()->isEmpty())
<div class="pb-4">This will delete your team. Beware! There is no coming back!</div>

View File

@ -1,3 +0,0 @@
<x-layout-subscription>
Cancel
</x-layout-subscription>

View File

@ -10,12 +10,16 @@
<span>Currently active team: <span
class="text-warning">{{ session('currentTeam.name') }}</span></span>
</div>
@if(request()->query->get('cancelled'))
<div class="rounded alert 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 (request()->query->get('cancelled'))
<div class="mb-6 rounded alert 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') !== null)
<livewire:subscription.pricing-plans />
@endif

View File

@ -1,3 +0,0 @@
<x-layout>
Success
</x-layout>

View File

@ -99,8 +99,6 @@
Route::get('/force-password-reset', [Controller::class, 'force_passoword_reset'])->name('auth.force-password-reset');
});
Route::get('/subscription', [Controller::class, 'subscription'])->name('subscription.index');
Route::get('/subscription/success', fn () => view('subscription.success'))->name('subscription.success');
Route::get('/subscription/cancel', fn () => view('profile'))->name('subscription.cancel');
Route::get('/settings', [Controller::class, 'settings'])->name('settings.configuration');
Route::get('/settings/license', [Controller::class, 'license'])->name('settings.license');
Route::get('/profile', fn () => view('profile', ['request' => request()]))->name('profile');

View File

@ -181,7 +181,7 @@
ray($email, $confirmation_code);
try {
$found = Waitlist::where('uuid', $confirmation_code)->where('email', $email)->first();
if ($found && !$found->verified && $found->created_at > now()->subMinutes(config('constants.waitlist.confirmation_valid_for_minutes'))) {
if ($found && !$found->verified && $found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) {
$found->verified = true;
$found->save();
send_internal_notification('Waitlist confirmed: ' . $email);
@ -267,9 +267,11 @@
break;
case 'customer.subscription.updated':
$subscriptionId = data_get($data, 'items.data.0.subscription');
$planId = data_get($data, 'items.data.0.plan.id');
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
$subscription = Subscription::where('stripe_subscription_id', $subscriptionId)->firstOrFail();
$subscription->update([
'stripe_plan_id' => $planId,
'stripe_cancel_at_period_end' => $cancelAtPeriodEnd,
]);
break;
@ -277,6 +279,8 @@
$subscriptionId = data_get($data, 'items.data.0.subscription');
$subscription = Subscription::where('stripe_subscription_id', $subscriptionId)->firstOrFail();
$subscription->update([
'stripe_subscription_id' => null,
'stripe_plan_id'=> null,
'stripe_cancel_at_period_end' => false,
'stripe_invoice_paid' => false,
]);