76 lines
2.5 KiB
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;
|
|
}
|
|
}
|