CluPilotCloud/app/Console/Commands/EndCancelledAddons.php

76 lines
2.5 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Actions\BookAddon;
use App\Models\SubscriptionAddon;
use Illuminate\Console\Command;
use Throwable;
/**
* End the modules whose cancellation date has arrived.
*
* A module is cancelled monthly and the cancellation lands at the end of the
* term the customer has already paid for — the same rule a package downgrade
* follows, and for the same reason: what has been paid for is kept. So
* BookAddon::cancelAtPeriodEnd() writes an appointment on the booking and leaves
* the module running. This keeps it.
*
* Stripe has already been told at the moment of cancellation; there is no item
* left to remove here. What ends now is the module itself — the entitlement, and
* for a storage pack the space on the machine — and the entry in the proof
* register that says the customer stopped paying for it.
*
* Safe to run as often as the scheduler likes: BookAddon::cancel() claims the
* cancellation conditionally, so a second tick over a module that has already
* ended writes nothing and records nothing.
*/
class EndCancelledAddons extends Command
{
protected $signature = 'clupilot:end-cancelled-addons
{--dry-run : list what would end and change nothing}';
protected $description = 'End the booked modules whose cancellation date has passed';
public function handle(BookAddon $bookAddon): int
{
$dryRun = (bool) $this->option('dry-run');
$ended = 0;
$due = SubscriptionAddon::query()
->whereNull('cancelled_at')
->whereNotNull('cancels_at')
->where('cancels_at', '<=', now())
->with('subscription')
->orderBy('id')
->get();
foreach ($due as $addon) {
$this->line(sprintf(
' %s on contract %s',
(string) $addon->addon_key,
(string) ($addon->subscription?->uuid ?? '—'),
));
if ($dryRun) {
continue;
}
try {
$bookAddon->cancel($addon);
$ended++;
} catch (Throwable $e) {
// One customer's module must not stop everybody else's from
// ending. It stays due and the next tick tries again.
$this->warn(" {$addon->uuid}: {$e->getMessage()}");
}
}
$this->info($dryRun
? "{$due->count()} module(s) due to end. Run without --dry-run to end them."
: "{$due->count()} module(s) due, {$ended} ended.");
return self::SUCCESS;
}
}