fix: cloud + stripe related

This commit is contained in:
Andras Bacsai
2024-11-11 22:18:12 +01:00
parent 1c00a37fc4
commit 1faa8be615
6 changed files with 62 additions and 141 deletions

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Console\Commands;
use App\Models\Team;
use Illuminate\Console\Command;
class CloudCheckSubscription extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cloud:check-subscription';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check Cloud subscriptions';
/**
* Execute the console command.
*/
public function handle()
{
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
$activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->get();
foreach ($activeSubscribers as $team) {
$stripeSubscriptionId = $team->subscription->stripe_subscription_id;
$stripeInvoicePaid = $team->subscription->stripe_invoice_paid;
$stripeCustomerId = $team->subscription->stripe_customer_id;
if (! $stripeSubscriptionId) {
echo "Team {$team->id} has no subscription, but invoice status is: {$stripeInvoicePaid}\n";
echo "Link on Stripe: https://dashboard.stripe.com/customers/{$stripeCustomerId}\n";
continue;
}
$subscription = $stripe->subscriptions->retrieve($stripeSubscriptionId);
if ($subscription->status === 'active') {
continue;
}
echo "Subscription {$stripeSubscriptionId} is not active ({$subscription->status})\n";
echo "Link on Stripe: https://dashboard.stripe.com/subscriptions/{$stripeSubscriptionId}\n";
}
}
}

View File

@@ -5,8 +5,6 @@ namespace App\Http\Controllers\Webhook;
use App\Http\Controllers\Controller;
use App\Jobs\ServerLimitCheckJob;
use App\Jobs\SubscriptionInvoiceFailedJob;
use App\Jobs\SubscriptionTrialEndedJob;
use App\Jobs\SubscriptionTrialEndsSoonJob;
use App\Models\Subscription;
use App\Models\Team;
use App\Models\Webhook;
@@ -260,42 +258,7 @@ class Stripe extends Controller
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
if ($team) {
$team->trialEnded();
}
$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,
]);
// send_internal_notification('customer.subscription.deleted for customer: '.$customerId);
break;
case 'customer.subscription.trial_will_end':
// Not used for now
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
if (! $team) {
return response('No team found for subscription: '.$subscription->id, 400);
}
SubscriptionTrialEndsSoonJob::dispatch($team);
break;
case 'customer.subscription.paused':
$customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
$team = data_get($subscription, 'team');
if (! $team) {
return response('No team found for subscription: '.$subscription->id, 400);
}
$team->trialEnded();
$subscription->update([
'stripe_trial_already_ended' => true,
'stripe_invoice_paid' => false,
]);
SubscriptionTrialEndedJob::dispatch($team);
// send_internal_notification('Subscription paused for customer: '.$customerId);
$team?->subscriptionEnded();
break;
default:
// Unhandled event type

View File

@@ -1,42 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\Team;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SubscriptionTrialEndedJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public Team $team
) {}
public function handle(): void
{
try {
$session = getStripeCustomerPortalSession($this->team);
$mail = new MailMessage;
$mail->subject('Action required: You trial in Coolify Cloud ended.');
$mail->view('emails.trial-ended', [
'stripeCustomerPortal' => $session->url,
]);
$this->team->members()->each(function ($member) use ($mail) {
if ($member->isAdmin()) {
send_user_an_email($mail, $member->email);
send_internal_notification('Trial reminder email sent to '.$member->email);
}
});
} catch (\Throwable $e) {
send_internal_notification('SubscriptionTrialEndsSoonJob failed with: '.$e->getMessage());
throw $e;
}
}
}

View File

@@ -1,42 +0,0 @@
<?php
namespace App\Jobs;
use App\Models\Team;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SubscriptionTrialEndsSoonJob implements ShouldBeEncrypted, ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public Team $team
) {}
public function handle(): void
{
try {
$session = getStripeCustomerPortalSession($this->team);
$mail = new MailMessage;
$mail->subject('You trial in Coolify Cloud ends soon.');
$mail->view('emails.trial-ends-soon', [
'stripeCustomerPortal' => $session->url,
]);
$this->team->members()->each(function ($member) use ($mail) {
if ($member->isAdmin()) {
send_user_an_email($mail, $member->email);
send_internal_notification('Trial reminder email sent to '.$member->email);
}
});
} catch (\Throwable $e) {
send_internal_notification('SubscriptionTrialEndsSoonJob failed with: '.$e->getMessage());
throw $e;
}
}
}

View File

@@ -2,8 +2,8 @@
namespace App\Livewire\Admin;
use App\Models\Team;
use App\Models\User;
use Illuminate\Container\Attributes\Auth as AttributesAuth;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
@@ -43,17 +43,13 @@ class Index extends Component
public function getSubscribers()
{
$this->inactiveSubscribers = User::whereDoesntHave('teams', function ($query) {
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
})->count();
$this->activeSubscribers = User::whereHas('teams', function ($query) {
$query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
})->count();
$this->inactiveSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', false)->count();
$this->activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->count();
}
public function switchUser(int $user_id)
{
if (AttributesAuth::id() !== 0) {
if (Auth::id() !== 0) {
return redirect()->route('dashboard');
}
$user = User::find($user_id);

View File

@@ -257,8 +257,15 @@ class Team extends Model implements SendsDiscord, SendsEmail
return $this->hasMany(S3Storage::class)->where('is_usable', true);
}
public function trialEnded()
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,
]);
foreach ($this->servers as $server) {
$server->settings()->update([
'is_usable' => false,
@@ -267,16 +274,6 @@ class Team extends Model implements SendsDiscord, SendsEmail
}
}
public function trialEndedButSubscribed()
{
foreach ($this->servers as $server) {
$server->settings()->update([
'is_usable' => true,
'is_reachable' => true,
]);
}
}
public function isAnyNotificationEnabled()
{
if (isCloud()) {