Skip to main content

Swapping Plans

The SwapPlan action changes a subscription’s plan mid-cycle. It handles proration, reconciles PlanItem quantities, and syncs with the gateway — all inside a database transaction.

Basic plan swap

use Pixelworxio\Subscribd\Actions\SwapPlan;
use Pixelworxio\Subscribd\Models\Plan;

$subscription = $user->subscription();
$newPlan = Plan::where('key', 'enterprise')->firstOrFail();

app(SwapPlan::class)->execute($subscription, $newPlan);
This:
  1. Computes a proration adjustment based on the configured strategy
  2. Updates the subscription at the gateway
  3. Reconciles SubscriptionItem records for PlanItems (adds, removes, or updates quantities)
  4. Updates the local subscription record
  5. Fires SubscriptionUpdated

Proration strategies

The strategy is set globally in config/subscribd.php and can be overridden per-swap via the options array:
// Global default
'proration_strategy' => env('SUBSCRIBD_PRORATION', 'now'),
StrategyBehaviour
nowImmediate prorated credit/charge (default)
renewalNew price applies at next renewal; no immediate charge
noneNew price at next renewal; no credit
// Override per-swap
app(SwapPlan::class)->execute($subscription, $newPlan, ['prorate' => 'renewal']);
For gateways that support native proration (Stripe), the strategy is forwarded to the gateway. For others, Subscribd’s built-in ProrationEngine computes the adjustment and applies it via a charge or credit balance sync. See Proration for a detailed breakdown with worked examples.

Overriding PlanItem quantities on swap

Pass a plan_items map to set specific quantities for the new plan’s items:
app(SwapPlan::class)->execute($subscription, $newPlan, [
    'plan_items' => [
        'extra_projects' => 5,
        'api_add_on'     => 2,
    ],
]);
Reconciliation rules:
  • Items present on both the old and new plan: price_snapshot is updated; quantity uses the override if provided, otherwise the existing quantity is preserved.
  • Items absent from the new plan: the SubscriptionItem is deleted.
  • Items new on the new plan: quantity uses the override if provided, otherwise the PlanItem’s included_quantity.
  • The gateway base item (with plan_item_key = null) is never touched by reconciliation.

Swap on a named subscription slot

$teamSub = $user->subscription('team');
$newPlan = Plan::where('key', 'team-enterprise')->firstOrFail();

app(SwapPlan::class)->execute($teamSub, $newPlan);

Listening to swap events

SwapPlan fires SubscriptionUpdated on success:
use Pixelworxio\Subscribd\Events\SubscriptionUpdated;

Event::listen(SubscriptionUpdated::class, function (SubscriptionUpdated $event): void {
    $subscription = $event->subscription;
    $billable     = $subscription->billable;

    // Send a plan-change confirmation email
    Mail::to($billable->billingEmail())->send(new PlanChangedMail($subscription));
});

Preventing swaps in your UI

Validate before calling SwapPlan to enforce business rules:
public function swap(Request $request): RedirectResponse
{
    $request->validate(['plan' => 'required|string']);

    $newPlan = Plan::where('key', $request->plan)->firstOrFail();
    $subscription = auth()->user()->subscription();

    // Prevent downgrade without confirmation
    if ($newPlan->amount < $subscription->plan->amount) {
        return redirect()->route('downgrade.confirm')
            ->with('new_plan', $newPlan->key);
    }

    app(SwapPlan::class)->execute($subscription, $newPlan);

    return redirect()->route('billing')->with('success', 'Plan updated.');
}

Using the SubscriptionManager Livewire component

The subscription-manager component handles plan swapping in the UI automatically, including a confirmation modal and proration preview:
<livewire:subscribd::subscriber.subscription-manager />
See Livewire Components for props and customisation.

Next steps