Laravel SaaS: Free Trial Implementation

Laravel is a good framework to create “software-as-a-service” solutions – with monthly payments by users. Usually, it should incorporate some kind of Free Trial mechanism. In this article, will show you my version of how to do it in Laravel 5.8.

What are we creating here

  • We take default Laravel app, run artisan make:auth for default registration form;
  • Will add a new timestamp field users.trial_until and will fill it by default with 14 days after registration;
  • Will make that 14 days configurable in config/app.php – you never know when business owner change their mind on the duration;
  • Show header text of how many free trial days are left, with link to upgrade to premium plan; (we won’t cover the upgrade or payments in this article, only Free Trial);
  • Check if free trial is over, if so – show a Bootstrap modal which cannot be turned off, so users would click to upgrade if they want to continue.

Ok, so the plan is clear, let’s move on.


Step 1. Users.trial_until field

We run this:

php artisan make:migration add_trial_until_field_to_users_table

Migration code:

Schema::table('users', function (Blueprint $table) {
    $table->timestamp('trial_until')->nullable();
});

Then we need to add this field to $fillable array in app/User.php:

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'password', 'trial_until'
    ];

Finally, we need to automatically fill it in on new user registration. So we add it to the array of app/Http/Controllers/Auth/RegisterController.php:

/**
 * Create a new user instance after a valid registration.
 *
 * @param  array  $data
 * @return \App\User
 */
protected function create(array $data)
{
    return User::create([
        'name' => $data['name'],
        'email' => $data['email'],
        'password' => Hash::make($data['password']),
        'trial_until' => now()->addDays(config('app.free_trial_days')),
    ]);
}

As you can see, we’re using a configurable value config(‘app.free_trial_days’), so let’s add the value at the end of config/app.php file:

    'aliases' => [

        'App' => Illuminate\Support\Facades\App::class,
        'Arr' => Illuminate\Support\Arr::class,
        // ...
        'View' => Illuminate\Support\Facades\View::class,

    ],

    'free_trial_days' => 14,

];

Step 2. Header Text and Upgrade Link

To show, how many days of free trial are left, we need to calculate that on every page. It would be convenient to have that as an attribute of app/User.php model, so we will use accessor method.

public function getFreeTrialDaysLeftAttribute()
{
    // Future field that will be implemented after payments
    if ($this->plan_until) { 
        return 0;
    }

    return now()->diffInDays($this->trial_until, false);
}

Now we will access this value by calling auth()->user()->free_trial_days_left from wherever in our code.

One thing – to use Carbon operations with $this->trial_until field, we need to add it to app/User.php array of $dates:

protected $dates = [
    'trial_until'
];

And then we go into our header which is located at resources/views/layouts/app.blade.php and let’s add another <li> item with info and link – see block after @else directive:

@guest
    <li class="nav-item">
        <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
    </li>
    @if (Route::has('register'))
        <li class="nav-item">
            <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
        </li>
    @endif
@else
    @if (auth()->user()->free_trial_days_left > 0)
    <li class="nav-item">
        <a class="nav-link" href="/upgrade">{{ auth()->user()->free_trial_days_left }} Days of Free Trial left. Upgrade now.</a>
    </li>
    @endif
    <li class="nav-item dropdown">
        <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
            {{ Auth::user()->name }} <span class="caret"></span>
        </a>

For now, we won’t implement that /upgrade link, it’s outside of the scope of this article. But we do show the days amount.


Step 3. Modal After Free Trial is Over

We will add one more @if-@else in the same file, at the end of resources/views/layouts/app.blade.php:

@if (auth()->check() && auth()->user()->free_trial_days_left < 0)
    <!-- Modal -->
    <div class="modal NO-fade"
         tabindex="-1" role="dialog" style="display: block">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h3 class="modal-title">Upgrade Plan</h3>
                </div>
                <div class="modal-body">
                    <div class="alert alert-danger">
                        Your Free Trial is over. Please choose plan to continue.
                    </div>
                    <div class="row">
                        <div class="col-md-6 offset-md-3 text-center">
                            <h4>Unlimited Plan</h4>
                            <b>$199.99 per year</b>
                            <hr />
                            - <b>Unlimited</b> AdminPanels<br />
                            - All CRUDs and Modules<br />
                            - Unlimited Functions<br />
                            - <b>Priority</b> Live-chat Support
                            <hr />
                            <a href="/upgrade" class="btn btn-lg btn-primary">Pay $199.99</a>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endif

</body>
</html>

Visual result:

I’ve taken the text example from our own QuickAdminPanel.com similar popup, you can make it more customizable and take the texts from the database or config.

So that’s it about free trial. If this article gets traction, I will probably continue this SaaS series with payments, plans, invoices etc. So shoot in the comments.

Like our articles?
Check out our Laravel online courses!

18 COMMENTS

  1. How to set value in view register and edit user if type is date and not text? Text type is showing but date type just YY.mm.dd

    • Sorry I didn’t understand your question, please rephrase with example or screenshots, cause now you mention register/edit/view in one sentence and I don’t understant.

        • 1. Wait, why do you want that field of date on register form? It doesn’t make sense, you’re allowing people to choose their trial until date?
          2. I think you’re using some datepicker with different format than yyyy-mm-dd so it cannot automatically format the value and show it, so it shows empty.

          • You’re right about the registry, but you have to consider this is testing. In any case, I am interested in how I can make a form and change the date on the trial column?

            I try default date format and still is empty?

  2. Can you try to make this field just input text? Pretty sure the value will appear then. Or maybe you made a typo somewhere in the field name. Sorry you need to debug such situation yourself, it’s out of this article scope.

  3. Great idea, there is a desktop software that I want to take to the web and implement as a saas, and all this comes in handy

  4. This is a very very very helpful tutorial. I’ve been needing to do this for a project I’ve been working on for a while now. Thank you very much.

    I’m running into an issue though. In my database, after registering, the trial_until field is showing “null”. Do you have any ideas what the problem could be?

    I’d really appreciate your quick response and I can’t till the second part of the tutorial.

    Once again, thanks

  5. Very helpful, thank you.

    I think there is a typo in the `getFreeTrialDaysLeftAttribute` function. Shouldn’t it be `if ($this->trial_until)` and not `if ($this->plan_until)`?

    Really want to see this go further to payments and such, thank you!
    Vadim.

    • Thank you, typo fixed!
      For now, this article series is on hold, have other priorities. But will definitely come back!

      • You know now that I realize it I was wrong. In the comment right abve that conditional you say “// Future field that will be implemented after payments” which means the code was correct. “$this->plan_until” would be the newly created date field for purchased plans. Because now using “$user->free_trial_days_left” will alwasy return 0.

        Sorry I think you had it correct all along!!!

LEAVE A REPLY

Please enter your comment!
Please enter your name here