# Subscriptions

# Creating subscriptions

To create a subscription, first retrieve an instance of your billable model, which typically will be an instance of App\Models\User. Once you have retrieved the model instance, you may use the newSubscription method to create the model's subscription:

use App\Models\User;

$user = User::find(1);

// Make sure to configure the 'premium' plan in config/cashier_plans.php
$result = $user->newSubscription('main', 'premium')->create();

If the customer already has a valid Mollie mandate, the $result will be a Subscription.

If the customer has no valid Mollie mandate yet, the $result will be a RedirectToCheckoutResponse, redirecting the customer to the Mollie checkout to make the first payment. Once the payment has been received the subscription will start.

# Example subscription controller

Here's a basic controller example for creating the subscription:

namespace App\Http\Controllers;

use Laravel\Cashier\SubscriptionBuilder\RedirectToCheckoutResponse;
use Illuminate\Support\Facades\Auth;

class CreateSubscriptionController extends Controller
{
    /**
     * @param string $plan
     * @return \Illuminate\Http\RedirectResponse
     */
    public function __invoke(string $plan)
    {
        $user = Auth::user();

        $name = ucfirst($plan) . ' membership';

        if(!$user->subscribed($name, $plan)) {

            $result = $user->newSubscription($name, $plan)->create();

            if(is_a($result, RedirectToCheckoutResponse::class)) {
                return $result; // Redirect to Mollie checkout
            }

            return back()->with('status', 'Welcome to the ' . $plan . ' plan');
        }

        return back()->with('status', 'You are already on the ' . $plan . ' plan');
    }
}

# Enforcing a redirect to Mollie's checkout

In order to always enforce a redirect to the Mollie checkout page and obtain a new mandate, use the newSubscriptionViaMollieCheckout method instead of newSubscription:

// make sure to configure the 'premium' plan in config/cashier.php
$redirect = $user->newSubscriptionViaMollieCheckout('main', 'premium')->create();

# Quantities

If you would like to set a specific quantity for the price when creating the subscription, you should invoke the quantity method on the subscription builder before creating the subscription:

$user->newSubscription($name, $plan)
    ->quantity(5)
    ->create();

# Coupons

Coupons allow you to apply special deals to subscriptions.

If you would like to apply a coupon when creating the subscription, you may use the withCoupon method:

$user->newSubscription($name, $plan)
    ->withCoupon('your-coupon-code')
    ->create();

The coupon will be validated when being applied and can throw a Laravel\Cashier\Exceptions\CouponException.

# Configuring subscription coupons

Coupons can be defined in config/cashier_coupons.php. Out of the box, a basic FixedDiscountHandler and PercentageDiscountHandler are provided.

Coupon handling in Cashier Mollie is designed with full flexibility in mind. You can provide your own coupon handler by extending \Cashier\Discount\BaseCouponHandler.

# Redeeming a coupon for an existing subscription

For redeeming a coupon for an existing subscription, use the redeemCoupon() method on the billable trait:

$user->redeemCoupon('your-coupon-code');

This will validate the coupon code and redeem it. The coupon will be applied to the upcoming Order.

Optionally, specify the subscription it should be applied to:

$user->redeemCoupon('your-coupon-code', 'main');

By default all other active redeemed coupons for the subscription will be revoked. You can prevent this by setting the $revokeOtherCoupons flag to false:

$user->redeemCoupon('your-coupon-code', 'main', false);

# Checking subscription status

if ($user->subscribed('main')) {
    //
}

The subscribed method also makes a great candidate for a route middleware (opens new window), allowing you to filter access to routes and controllers based on the user's subscription status:

<?php

namespace App\Http\Middleware;

use Closure;

class EnsureUserIsSubscribed
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        if ($request->user() && ! $request->user()->subscribed('default')) {
            // This user is not a paying customer...
            return redirect('billing');
        }

        return $next($request);
    }
}

If you would like to determine if a user is still within their trial period, you may use the onTrial method. This method can be useful for displaying a warning to the user that they are still on their trial period:

if ($user->subscription('main')->onTrial()) {
    //
}

The subscribedToPlan method may be used to determine if the user is subscribed to a given plan based on a configured plan. In this example, we will determine if the user's main subscription is actively subscribed to the monthly plan:

if ($user->subscribedToPlan('monthly', 'main')) {
    //
}

# Cancelled subscription status

To determine if the user was once an active subscriber, but has cancelled their subscription, you may use the cancelled method:

if ($user->subscription('main')->cancelled()) {
    //
}

You may also determine if a user has cancelled their subscription, but are still on their "grace period" until the subscription fully expires. For example, if a user cancels a subscription on March 5th that was originally scheduled to expire on March 10th, the user is on their "grace period" until March 10th. Note that the subscribed method still returns true during this time:

if ($user->subscription('main')->onGracePeriod()) {
    //
}

# Changing plans

After a user is subscribed to your application, they may occasionally want to change to a new subscription plan. To swap a user to a new subscription plan, pass the plan identifier to the swap or swapNextCycle method:

use App\Models\User;

$user = App\User::find(1);

// Swap right now
$user->subscription('main')->swap('other-plan-id');

// Swap once the current cycle has completed
$user->subscription('main')->swapNextCycle('other-plan-id');

If the user is on trial, the trial period will be maintained. Also, if a "quantity" exists for the subscription, that quantity will also be maintained.

# Subscription quantity

Sometimes subscriptions are affected by "quantity". For example, your application might charge €10 per month per seat. To easily increment or decrement the subscription quantity, use the incrementQuantity, decrementQuantity and updateQuantity methods:

use App\Models\User;

$user = App\User::find(1);

// Increment the subscription quantity by one
$user->subscription('main')->incrementQuantity();

// Add five to the subscription's current quantity...
$user->subscription('main')->incrementQuantity(5);

// Decrement the subscription quantity by one
$user->subscription('main')->decrementQuantity();

// Subtract five to the subscription's current quantity...
$user->subscription('main')->decrementQuantity(5);

// Provide a specific subscription quantity
$user->subscription('main')->updateQuantity(10);

# Subscription taxes

To specify the tax percentage a user pays on a subscription, implement the taxPercentage method on your billable model, and return a numeric value between 0 and 100, with no more than 2 decimal places.

public function taxPercentage() {
    return 20;
}

The taxPercentage method enables you to apply a tax rate on a model-by-model basis, which may be helpful for a user base that spans multiple countries and tax rates.

# Syncing tax percentages

When changing the hard-coded value returned by the taxPercentage method, the tax settings on any existing subscriptions for the user will remain the same. If you wish to update the tax value for existing subscriptions with the returned taxPercentage value, you should call the syncTaxPercentage method on the user's subscription instance:

$user->subscription('main')->syncTaxPercentage();

# Cancelling subscriptions

To cancel a subscription, call the cancel method on the user's subscription:

$user->subscription('main')->cancel();

When a subscription is cancelled, Cashier will automatically set the ends_at column in your subscriptions database table. This column is used to know when the subscribed method should begin returning false.

For example, if a customer cancels a subscription on March 1st, but the subscription was not scheduled to end until March 5th, the subscribed method will continue to return true until March 5th. This is done because a user is typically allowed to continue using an application until the end of their billing cycle.

You may determine if a user has cancelled their subscription and is still on their "grace period" using the onGracePeriod method:

if ($user->subscription('main')->onGracePeriod()) {
    //
}

If you wish to cancel a subscription immediately, call the cancelNow method on the user's subscription:

$user->subscription('main')->cancelNow();

# Resuming subscriptions

If a user has cancelled their subscription, and you wish to resume it, use the resume method. The user must still be on their grace period in order to resume a subscription:

$user->subscription('main')->resume();

If a subscription gets cancelled and resumed before the subscription has fully expired, the user will not be billed immediately. Instead, their subscription will be reactivated, and they will be billed on the original billing cycle.

# Subscription Scopes

You can query your database for subscriptions in a specific state using query scopes. Here are some examples:

$activeSubscriptions = Subscription::whereActive()->get();

$cancelledSubscriptions = Subscription::whereCancelled()->get();

The complete list of available query scopes:

Subscription::whereOwner($user);

Subscription::whereActive();
Subscription::whereNotActive();

Subscription::whereCancelled();
Subscription::whereNotCancelled();

Subscription::whereOnTrial();
Subscription::whereNotOnTrial();

Subscription::whereOnGracePeriod();
Subscription::whereNotOnGracePeriod();

Subscription::whereRecurring();
Subscription::whereNotRecurring();