Skip to main content
UpgradeFromFree transitions a free subscription to a paid plan on a real payment gateway. It cancels the old free subscription locally, creates a new paid subscription on the target gateway, hydrates plan items, and fires SubscriptionCreated — all inside a single database transaction.

Calling styles

Resolve the action from the container:
use Pixelworxio\Subscribd\Actions\UpgradeFromFree;
use Pixelworxio\Subscribd\Models\Plan;

$freeSubscription = $user->subscription('default');
$paidPlan = Plan::where('key', 'pro')->firstOrFail();

$newSubscription = app(UpgradeFromFree::class)->execute(
    $user,
    $freeSubscription,
    $paidPlan,
    ['gateway' => 'stripe'],
);

Parameters

ParameterTypeRequiredDescription
$billableModel&BillableYesThe billable that owns the free subscription
$freeSubscriptionSubscriptionYesThe existing subscription in free status
$planPlanYesThe paid plan to upgrade to
$optionsarray<string, mixed>NoAdditional options (see below)

Options array

KeyTypeDefaultDescription
gatewaystringconfig('subscribd.default')Payment gateway driver name. Cannot be null (the null driver).
namestringInherited from $freeSubscription->nameSubscription slot name for the new subscription
quantityintResolved via QuantityResolverRegistry, then 1Number of seats or units
plan_itemsarray<string, int>Map of plan item key to quantity override

How it works

  1. Validates that the subscription is in free status. Throws SubscriptionException if not.
  2. Validates that the target gateway is not the null driver.
  3. Resolves the gateway driver and creates a customer record if one does not exist.
  4. Creates the new paid subscription on the gateway.
  5. Hydrates plan items for the new subscription from the plan’s active plan items.
  6. Cancels the old free subscription locally by transitioning it to canceled and setting canceled_at. No gateway API call is made — the null gateway has no remote state.
  7. Fires SubscriptionCreated for the new paid subscription.

Return value

Returns the new Subscription model in active status with the plan relationship loaded.
$newSubscription->status;   // 'active'
$newSubscription->gateway;  // 'stripe'
$newSubscription->name;     // 'default' (inherited from the free subscription)

Errors

ExceptionWhen
SubscriptionExceptionThe subscription is not in free status (invalid state transition).
SubscriptionExceptionThe resolved gateway is the null driver ('null').
GatewayExceptionThe gateway fails to create the subscription.

Events fired

SubscriptionCreated is dispatched for the new paid subscription after the transaction commits.
SubscriptionCanceled is not fired for the old free subscription. The cancellation is an internal housekeeping step, not a user-initiated cancellation. This avoids triggering offboarding workflows for what is actually an upgrade.