CluPilotCloud/app/Console/Commands/SyncStripeSubscriptions.php

71 lines
2.6 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Actions\MoveStripeSubscriptionPrice;
use App\Models\Subscription;
use Illuminate\Console\Command;
/**
* The sweep behind the plan change.
*
* Moving Stripe onto the new price at the moment the change lands is right and
* it is not enough. Stripe can be unreachable, the key can have been rotated,
* the catalogue can be a sync behind — and the change has already happened to
* the contract and to the machine by then, so it is not rolled back. What is
* left is a customer being charged for a package they are no longer on, and
* nothing about the contract would ever say so again.
*
* ApplyPlanChange parks exactly that on the contract. This picks it up, however
* long ago it happened.
*
* Deliberately hands the work back to the action rather than doing it here: the
* action owns which price, which item and which proration behaviour, and two
* code paths that both move a subscription is one more than the number that can
* be kept in agreement.
*/
class SyncStripeSubscriptions extends Command
{
protected $signature = 'clupilot:sync-stripe-subscriptions {--limit=100}';
protected $description = 'Retry the plan changes that landed here but never reached Stripe';
public function handle(MoveStripeSubscriptionPrice $move): int
{
// Active contracts only. A cancelled one is not billed again, so moving
// it onto another price would be housekeeping on something Stripe has
// already finished with — and an upgrade's proration raised against a
// contract that has ended would charge a departed customer.
$parked = Subscription::query()
->whereNotNull('stripe_price_sync')
->whereNotNull('stripe_subscription_id')
->where('status', 'active')
->orderBy('id')
->limit((int) $this->option('limit'))
->get();
$moved = 0;
foreach ($parked as $subscription) {
if ($move->retry($subscription)) {
$moved++;
continue;
}
// The action has already logged why and counted the attempt. Said
// again here because somebody is watching this command's output
// and not the log.
$this->warn(sprintf(
'Contract %s: still billed at the old price — %s',
$subscription->uuid,
(string) ($subscription->fresh()?->stripe_price_sync['error'] ?? 'unknown'),
));
}
$this->info("{$parked->count()} contract(s) out of step with Stripe, {$moved} moved.");
return self::SUCCESS;
}
}