feat(cloud-check): enhance CloudCheckSubscription command with fix options
- Added options to the CloudCheckSubscription command for fixing canceled subscriptions in the database. - Implemented a dry-run mode to preview changes without applying them. - Introduced a flag to limit checks/fixes to the first found subscription, improving command usability and control.
This commit is contained in:
@@ -12,7 +12,10 @@ class CloudCheckSubscription extends Command
|
|||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $signature = 'cloud:check-subscription';
|
protected $signature = 'cloud:check-subscription
|
||||||
|
{--fix : Fix canceled subscriptions in database}
|
||||||
|
{--dry-run : Show what would be fixed without making changes}
|
||||||
|
{--one : Only check/fix the first found subscription}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The console command description.
|
* The console command description.
|
||||||
@@ -27,6 +30,11 @@ class CloudCheckSubscription extends Command
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||||
|
|
||||||
|
if ($this->option('fix') || $this->option('dry-run')) {
|
||||||
|
return $this->fixCanceledSubscriptions($stripe);
|
||||||
|
}
|
||||||
|
|
||||||
$activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->get();
|
$activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->get();
|
||||||
|
|
||||||
$out = fopen('php://output', 'w');
|
$out = fopen('php://output', 'w');
|
||||||
@@ -93,4 +101,219 @@ class CloudCheckSubscription extends Command
|
|||||||
|
|
||||||
fclose($out);
|
fclose($out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix canceled subscriptions in the database
|
||||||
|
*/
|
||||||
|
private function fixCanceledSubscriptions(\Stripe\StripeClient $stripe)
|
||||||
|
{
|
||||||
|
$isDryRun = $this->option('dry-run');
|
||||||
|
$checkOne = $this->option('one');
|
||||||
|
|
||||||
|
if ($isDryRun) {
|
||||||
|
$this->info('DRY RUN MODE - No changes will be made');
|
||||||
|
if ($checkOne) {
|
||||||
|
$this->info('Checking only the first canceled subscription...');
|
||||||
|
} else {
|
||||||
|
$this->info('Checking for canceled subscriptions...');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($checkOne) {
|
||||||
|
$this->info('Checking and fixing only the first canceled subscription...');
|
||||||
|
} else {
|
||||||
|
$this->info('Checking and fixing canceled subscriptions...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$teamsWithSubscriptions = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->get();
|
||||||
|
$toFixCount = 0;
|
||||||
|
$fixedCount = 0;
|
||||||
|
$errors = [];
|
||||||
|
$canceledSubscriptions = [];
|
||||||
|
|
||||||
|
foreach ($teamsWithSubscriptions as $team) {
|
||||||
|
$subscription = $team->subscription;
|
||||||
|
|
||||||
|
if (! $subscription->stripe_subscription_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$stripeSubscription = $stripe->subscriptions->retrieve(
|
||||||
|
$subscription->stripe_subscription_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($stripeSubscription->status === 'canceled') {
|
||||||
|
$toFixCount++;
|
||||||
|
|
||||||
|
// Get team members' emails
|
||||||
|
$memberEmails = $team->members->pluck('email')->toArray();
|
||||||
|
|
||||||
|
$canceledSubscriptions[] = [
|
||||||
|
'team_id' => $team->id,
|
||||||
|
'team_name' => $team->name,
|
||||||
|
'customer_id' => $subscription->stripe_customer_id,
|
||||||
|
'subscription_id' => $subscription->stripe_subscription_id,
|
||||||
|
'status' => 'canceled',
|
||||||
|
'member_emails' => $memberEmails,
|
||||||
|
'subscription_model' => $subscription->toArray(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($isDryRun) {
|
||||||
|
$this->warn('Would fix canceled subscription:');
|
||||||
|
$this->line(" Team ID: {$team->id}");
|
||||||
|
$this->line(" Team Name: {$team->name}");
|
||||||
|
$this->line(' Team Members: '.implode(', ', $memberEmails));
|
||||||
|
$this->line(" Customer URL: https://dashboard.stripe.com/customers/{$subscription->stripe_customer_id}");
|
||||||
|
$this->line(" Subscription URL: https://dashboard.stripe.com/subscriptions/{$subscription->stripe_subscription_id}");
|
||||||
|
$this->line(' Current Subscription Data:');
|
||||||
|
foreach ($subscription->getAttributes() as $key => $value) {
|
||||||
|
if (is_null($value)) {
|
||||||
|
$this->line(" - {$key}: null");
|
||||||
|
} elseif (is_bool($value)) {
|
||||||
|
$this->line(" - {$key}: ".($value ? 'true' : 'false'));
|
||||||
|
} else {
|
||||||
|
$this->line(" - {$key}: {$value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->newLine();
|
||||||
|
} else {
|
||||||
|
$this->warn("Found canceled subscription for Team ID: {$team->id}");
|
||||||
|
|
||||||
|
// Send internal notification with all details before fixing
|
||||||
|
$notificationMessage = "Fixing canceled subscription:\n";
|
||||||
|
$notificationMessage .= "Team ID: {$team->id}\n";
|
||||||
|
$notificationMessage .= "Team Name: {$team->name}\n";
|
||||||
|
$notificationMessage .= 'Team Members: '.implode(', ', $memberEmails)."\n";
|
||||||
|
$notificationMessage .= "Customer URL: https://dashboard.stripe.com/customers/{$subscription->stripe_customer_id}\n";
|
||||||
|
$notificationMessage .= "Subscription URL: https://dashboard.stripe.com/subscriptions/{$subscription->stripe_subscription_id}\n";
|
||||||
|
$notificationMessage .= "Subscription Data:\n";
|
||||||
|
foreach ($subscription->getAttributes() as $key => $value) {
|
||||||
|
if (is_null($value)) {
|
||||||
|
$notificationMessage .= " - {$key}: null\n";
|
||||||
|
} elseif (is_bool($value)) {
|
||||||
|
$notificationMessage .= " - {$key}: ".($value ? 'true' : 'false')."\n";
|
||||||
|
} else {
|
||||||
|
$notificationMessage .= " - {$key}: {$value}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
send_internal_notification($notificationMessage);
|
||||||
|
|
||||||
|
// Apply the same logic as customer.subscription.deleted webhook
|
||||||
|
$team->subscriptionEnded();
|
||||||
|
|
||||||
|
$fixedCount++;
|
||||||
|
$this->info(" ✓ Fixed subscription for Team ID: {$team->id}");
|
||||||
|
$this->line(' Team Members: '.implode(', ', $memberEmails));
|
||||||
|
$this->line(" Customer URL: https://dashboard.stripe.com/customers/{$subscription->stripe_customer_id}");
|
||||||
|
$this->line(" Subscription URL: https://dashboard.stripe.com/subscriptions/{$subscription->stripe_subscription_id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break if --one flag is set
|
||||||
|
if ($checkOne) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Stripe\Exception\InvalidRequestException $e) {
|
||||||
|
if ($e->getStripeCode() === 'resource_missing') {
|
||||||
|
$toFixCount++;
|
||||||
|
|
||||||
|
// Get team members' emails
|
||||||
|
$memberEmails = $team->members->pluck('email')->toArray();
|
||||||
|
|
||||||
|
$canceledSubscriptions[] = [
|
||||||
|
'team_id' => $team->id,
|
||||||
|
'team_name' => $team->name,
|
||||||
|
'customer_id' => $subscription->stripe_customer_id,
|
||||||
|
'subscription_id' => $subscription->stripe_subscription_id,
|
||||||
|
'status' => 'missing',
|
||||||
|
'member_emails' => $memberEmails,
|
||||||
|
'subscription_model' => $subscription->toArray(),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($isDryRun) {
|
||||||
|
$this->error('Would fix missing subscription (not found in Stripe):');
|
||||||
|
$this->line(" Team ID: {$team->id}");
|
||||||
|
$this->line(" Team Name: {$team->name}");
|
||||||
|
$this->line(' Team Members: '.implode(', ', $memberEmails));
|
||||||
|
$this->line(" Customer URL: https://dashboard.stripe.com/customers/{$subscription->stripe_customer_id}");
|
||||||
|
$this->line(" Subscription ID (missing): {$subscription->stripe_subscription_id}");
|
||||||
|
$this->line(' Current Subscription Data:');
|
||||||
|
foreach ($subscription->getAttributes() as $key => $value) {
|
||||||
|
if (is_null($value)) {
|
||||||
|
$this->line(" - {$key}: null");
|
||||||
|
} elseif (is_bool($value)) {
|
||||||
|
$this->line(" - {$key}: ".($value ? 'true' : 'false'));
|
||||||
|
} else {
|
||||||
|
$this->line(" - {$key}: {$value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->newLine();
|
||||||
|
} else {
|
||||||
|
$this->error("Subscription not found in Stripe for Team ID: {$team->id}");
|
||||||
|
|
||||||
|
// Send internal notification with all details before fixing
|
||||||
|
$notificationMessage = "Fixing missing subscription (not found in Stripe):\n";
|
||||||
|
$notificationMessage .= "Team ID: {$team->id}\n";
|
||||||
|
$notificationMessage .= "Team Name: {$team->name}\n";
|
||||||
|
$notificationMessage .= 'Team Members: '.implode(', ', $memberEmails)."\n";
|
||||||
|
$notificationMessage .= "Customer URL: https://dashboard.stripe.com/customers/{$subscription->stripe_customer_id}\n";
|
||||||
|
$notificationMessage .= "Subscription ID (missing): {$subscription->stripe_subscription_id}\n";
|
||||||
|
$notificationMessage .= "Subscription Data:\n";
|
||||||
|
foreach ($subscription->getAttributes() as $key => $value) {
|
||||||
|
if (is_null($value)) {
|
||||||
|
$notificationMessage .= " - {$key}: null\n";
|
||||||
|
} elseif (is_bool($value)) {
|
||||||
|
$notificationMessage .= " - {$key}: ".($value ? 'true' : 'false')."\n";
|
||||||
|
} else {
|
||||||
|
$notificationMessage .= " - {$key}: {$value}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
send_internal_notification($notificationMessage);
|
||||||
|
|
||||||
|
// Apply the same logic as customer.subscription.deleted webhook
|
||||||
|
$team->subscriptionEnded();
|
||||||
|
|
||||||
|
$fixedCount++;
|
||||||
|
$this->info(" ✓ Fixed missing subscription for Team ID: {$team->id}");
|
||||||
|
$this->line(' Team Members: '.implode(', ', $memberEmails));
|
||||||
|
$this->line(" Customer URL: https://dashboard.stripe.com/customers/{$subscription->stripe_customer_id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break if --one flag is set
|
||||||
|
if ($checkOne) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$errors[] = "Team ID {$team->id}: ".$e->getMessage();
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$errors[] = "Team ID {$team->id}: ".$e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->newLine();
|
||||||
|
$this->info('Summary:');
|
||||||
|
|
||||||
|
if ($isDryRun) {
|
||||||
|
$this->info(" - Found {$toFixCount} canceled/missing subscriptions that would be fixed");
|
||||||
|
|
||||||
|
if ($toFixCount > 0) {
|
||||||
|
$this->newLine();
|
||||||
|
$this->comment('Run with --fix to apply these changes');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->info(" - Fixed {$fixedCount} canceled/missing subscriptions");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($errors)) {
|
||||||
|
$this->newLine();
|
||||||
|
$this->error('Errors encountered:');
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
$this->error(" - {$error}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user