71 lines
2.6 KiB
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;
|
|
}
|
|
}
|