> ## Documentation Index
> Fetch the complete documentation index at: https://docs.subscribd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating Subscriptions

> Subscribe billable models to plans using the CreateSubscription action

# Creating Subscriptions

Subscriptions are created using the `CreateSubscription` action. Inject it via the service container or resolve it with `app()`.

## Basic subscription

```php theme={null}
use Pixelworxio\Subscribd\Actions\CreateSubscription;
use Pixelworxio\Subscribd\Models\Plan;

$plan = Plan::where('key', 'pro')->firstOrFail();

app(CreateSubscription::class)->execute($user, $plan);
```

This creates the subscription record, creates or retrieves the customer at the gateway, stores the payment method, issues the first invoice, and dispatches the `SubscriptionCreated` event.

## Subscription options

Pass an options array as the third argument:

```php theme={null}
app(CreateSubscription::class)->execute($user, $plan, [
    'name'     => 'default',    // subscription slot name, defaults to 'default'
    'gateway'  => 'stripe',     // override the default gateway
    'coupon'   => 'SAVE20',     // apply a coupon code at checkout
]);
```

## PlanItem quantities

When a plan has PlanItems (composable add-ons), pass a `plan_items` map to set per-item quantities at subscription time:

```php theme={null}
app(CreateSubscription::class)->execute($user, $plan, [
    'plan_items' => [
        'extra_projects' => 3,
        'api_add_on'     => 1,
    ],
]);
```

Omitted keys default to the PlanItem's `included_quantity`.

## Trials

If the plan defines `trial_days`, the trial starts automatically. No payment is collected until the trial ends.

To start a trial without collecting a payment method upfront, set `trial_requires_payment_method` to `false` on the plan:

```php theme={null}
// config/subscribd.php
'plans' => [
    'pro' => [
        'trial_days'                      => 14,
        'trial_requires_payment_method'   => false,
        // ...
    ],
],
```

With this setting, `CreateSubscription` skips the gateway entirely. A `gateway_id` is assigned later when `ConvertTrialToActive` runs. See [Trial Management](/subscriptions/trials) for the full trial lifecycle.

## Gateway selection

Subscriptions use the `default` gateway from config unless overridden:

```php theme={null}
// Use PayPal instead of the default gateway
app(CreateSubscription::class)->execute($user, $plan, ['gateway' => 'paypal']);
```

## Zero-cost plans

Plans with `amount = 0` are automatically routed to the `null` gateway — no payment method is required and no API call is made:

```php theme={null}
$freePlan = Plan::where('key', 'free')->firstOrFail();
app(CreateSubscription::class)->execute($user, $freePlan);  // no gateway call
```

## Named subscriptions (multi-slot)

A user can hold multiple concurrent subscriptions by using different slot names:

```php theme={null}
$individualPlan = Plan::where('key', 'pro')->firstOrFail();
$teamPlan       = Plan::where('key', 'team-pro')->firstOrFail();

app(CreateSubscription::class)->execute($user, $individualPlan, ['name' => 'individual']);
app(CreateSubscription::class)->execute($user, $teamPlan,       ['name' => 'team']);

// Access each slot
$user->subscription('individual');
$user->subscription('team');
```

See [Multiple Subscriptions](/subscriptions/multiple-slots) for the full multi-slot pattern.

## Handling gateway exceptions

For gateways that require Strong Customer Authentication (SCA/3DS), `CreateSubscription` may throw `RequiresActionException`:

```php theme={null}
use Pixelworxio\Subscribd\Exceptions\RequiresActionException;
use Pixelworxio\Subscribd\Exceptions\SubscriptionException;

try {
    $subscription = app(CreateSubscription::class)->execute($user, $plan);
} catch (RequiresActionException $e) {
    // Redirect the user to complete 3DS confirmation
    return redirect($e->redirectUrl());
} catch (SubscriptionException $e) {
    // Gateway rejected the subscription (e.g. Braintree with interval_count > 1)
    report($e);
    return back()->withErrors(['billing' => $e->getMessage()]);
}
```

## Webhook-activated gateways (Paddle)

Paddle returns an `Incomplete` subscription with a `checkout_url` in `meta`. Redirect the user to complete payment:

```php theme={null}
$subscription = app(CreateSubscription::class)->execute($user, $plan, ['gateway' => 'paddle']);

if ($subscription->status->value === 'incomplete') {
    return redirect($subscription->meta['checkout_url']);
}
```

`SubscriptionCreated` fires from the webhook handler once Paddle confirms payment, not from `CreateSubscription`.

## Listening to the SubscriptionCreated event

```php theme={null}
use Pixelworxio\Subscribd\Events\SubscriptionCreated;

class SendWelcomeEmail
{
    public function handle(SubscriptionCreated $event): void
    {
        $billable = $event->subscription->billable;
        $plan     = $event->subscription->plan;

        Mail::to($billable->billingEmail())->send(new WelcomeMail($plan));
    }
}
```

Register in `AppServiceProvider` or a dedicated provider:

```php theme={null}
Event::listen(SubscriptionCreated::class, SendWelcomeEmail::class);
```

## Testing

Use the `fake` gateway in tests. It processes subscriptions synchronously without any HTTP calls:

```php theme={null}
use Pixelworxio\Subscribd\Actions\CreateSubscription;
use Pixelworxio\Subscribd\Models\Plan;

it('subscribes a user to the pro plan', function () {
    $user = User::factory()->create();
    $plan = Plan::factory()->create(['key' => 'pro', 'amount' => 4900]);

    app(CreateSubscription::class)->execute($user, $plan);

    expect($user->subscribed())->toBeTrue();
    expect($user->subscription()->plan->key)->toBe('pro');
});
```

See [Testing](/advanced/testing) for the full `FakeGateway` setup.

## Next steps

* **[Subscription Status](/subscriptions/status)** — Check subscription state and dates
* **[Trials](/subscriptions/trials)** — Manage free trial periods
* **[Swapping Plans](/subscriptions/swapping)** — Upgrade or downgrade mid-cycle
