Key Takeaways
- Recurly is a service that simplifies the process of scheduling and processing recurring payments, handling issues such as payment failures, updating billing details, and managing plan changes. It can be integrated into a Laravel-based website using its JavaScript libraries.
- A subscription-based website can be set up using Laravel and Recurly, starting with basic registration, authentication, user roles and permissions. This is followed by integrating payment processing into the registration process to allow for different membership tiers.
- Laravel offers a system called migrations to create and modify database tables programmatically, making it easier to set up a users table for the website. Authority, a library downloaded via Composer, can be used to establish user roles and permissions.
- Laravel also provides an easy way to set up a login mechanism for users. This is achieved by creating a route to the login page, building a login form, and processing the login using the Auth::attempt() function. A similar process is used to log users out.
- A basic registration process can be set up in Laravel, involving creating a registration route, building a registration form, and implementing the POST action. This involves creating a validator, passing it the POST variables, and setting up validation rules. If validation passes, a new user is created, assigned to the pending role, logged in, and redirected to the front page.
Setting Up the Application
Start by creating a new project folder and run the following command:composer create-project laravel/laravel recurly --prefer-distThis creates a new Laravel project using Composer, which downloads the framework and its dependencies. We also need some additional libraries for later, so add the following in the require section of
composer.json
and run composer.phar update
.
"machuga/authority": "dev-develop",
"machuga/authority-l4" : "dev-master",
"recurly/recurly-client": "2.1.*@dev"
This downloads the Authority library, which we’ll use for user roles and permissions, and the Recurly client library.
Next, configure the database connection by specifying the appropriate schema name, username, password and hostname/address in app/config/database.php
. Laravel works out-of-the-box with a number of databases, such as MySQL, Postgres, SQLite, and SQL Server.
We also need a users table. Rather than set that up by hand, Laravel provides migrations, a means to create and modify database tables programmatically. Run the following command:
php artisan migrate:make create_users_tableThis creates the migration file in the
app/database/migrations
folder. The file is created with a basic outline, but let’s flesh it out with our table definition:
public function up()
{
Schema::create('users', function($table) {
$table->increments('id');
$table->string('email')->unique();
$table->string('name');
$table->string('password');
$table->timestamps();
});
}
public function down()
{
Schema::drop('users');
}
Go back to the command line and run:
php artisan migrateLook in the database and you should find that Laravel has created the users table for you.
Setting up Authority for Roles and Permissions
To determine the type of account a user has and privileges it grants them, we’ll assign users to a role. We’ve already downloaded Authority via Composer to help with this; we just need to carry out a few more steps to configure it fully. Inapp/config/app.php
, add the following line to the providers:
'Authority\AuthorityL4\AuthorityL4ServiceProvider',
Add the following to the aliases:
'Authority' => 'Authority\AuthorityL4\Facades\Authority',
And publish the Authority configuration file:
php artisan config:publish machuga/authority-l4We’ll return to the configuration file later. For now, we need to create some additional database tables. Fortunately, the package contains its own migrations for this. Run them with the following command:
php artisan migrate --package="machuga/authority-l4"You’ll find you have three additional tables:
permissions
, roles
and role_user
.
We also need to create models to represent roles and permissions. We’ll keep them simple for now. In app/models/Role.php
:
<?php
class Role extends Eloquent
{
}
And in app/models/Permission.php:
<?php
class Permission extends Eloquent
{
}
Now we need to modify the User
model class – which has already been created – to associate a user with roles and permissions. Add the following lines to app/models/User.php
:
public function roles() {
return $this->belongsToMany('Role');
}
public function permissions() {
return $this->hasMany('Permission');
}
public function hasRole($key) {
foreach($this->roles as $role){
if ($role->name === $key) {
return true;
}
}
return false;
}
Now let’s pre-populate the database with some data. Open app/database/seeds/DatabaseSeeder.php
and paste in the following:
<?php
class DatabaseSeeder extends Seeder
{
public function run() {
Eloquent::unguard();
$this->call('UserTableSeeder');
$this->command->info('User table seeded!');
$this->call('RoleTableSeeder');
$this->command->info('Role table seeded!');
}
}
class UserTableSeeder extends Seeder
{
public function run() {
DB::table('users')->delete();
User::create(array(
'email' => 'joe.bloggs@example.com',
'name' => 'Joe Bloggs',
'password' => Hash::make('password')
));
}
}
class RoleTableSeeder extends Seeder
{
public function run() {
DB::table('roles')->delete();
Role::create(array('name' => 'admin'));
Role::create(array('name' => 'pending'));
Role::create(array('name' => 'member'));
Role::create(array('name' => 'bronze'));
Role::create(array('name' => 'silver'));
Role::create(array('name' => 'gold'));
}
}
Then seed the database by running:
php artisan db:seed
Creating a Layout
Now we’ll create the overall page layout. Download Twitter Bootstrap and put the source files in thepublic
folder – moving the js
files to public/js/libs
.
Create the file app/views/layouts/default.blade.php
with the following contents:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Subscription Site Tutorial</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<!-- Le styles -->
<link href="/css/bootstrap.css" rel="stylesheet">
<link href="/css/style.css" rel="stylesheet">
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="brand" href="#">Subscription Site Tutorial</a>
<div class="nav-collapse collapse">
<ul class="nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="#about">About</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
</div>
<div class="container">
@if(Session::has('success'))
<div class="alert alert-success">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ Session::get('success') }}
</div>
@endif
@if(Session::has('error'))
<div class="alert alert-error">
<button type="button" class="close" data-dismiss="alert">×</button>
{{ Session::get('error') }}
</div>
@endif
@yield('content')
</div> <!-- /container -->
<!-- Le javascript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="/js/libs/bootstrap.min.js"></script>
</body>
</html>
It’s pretty basic stuff – our page content is outputted in the line that reads @yield('content')
.
Next, create the file public/css/style.css
:
body {
padding-top: 50px;
}
p.logged-in {
color: white;
margin-top: 0.5em;
}
And finally, let’s create a basic home page for the application. Create the file app/views/home/index.blade.php
:
@extends('layouts.default')
@section('content')
<h1>Subscription Site Tutorial</h1>
@stop
The @extends
declaration tells Laravel to use the default layout we just created with the content we’re injecting into the content area via the @yield
command wrapped in the @section
declaration.
Don’t forget to change the default route accordingly in app/routes.php
:
Route::get('/', function() {
return View::make('home/index');
});
Building the Login Mechanism
We’ve got our users – and an account to start off with – so now we need the ability to log in. Inapp/routes.php
, add a route to the login page:
Route::get('/auth/login', function() {
return View::make('auth/login');
});
Now create the view, app/views/auth/login.blade.php
. The .blade.php
extension indicates that we’re going to use the Blade templating library which ships with Laravel, which is cleaner than straight PHP. Behind the scenes, these template files are compiled into PHP as required.
@extends('layouts.default')
@section('content')
<h1>Please Log in</h1>
{{ Form::open(array('url' => 'auth/login')) }}
{{ Form::label('email', 'E-Mail Address') }}
{{ Form::text('email') }}
{{ Form::label('password', 'Password') }}
{{ Form::password('password') }}
<div class="form-actions">
{{ Form::submit('Login', array('class' => 'btn btn-primary')) }}
</div>
{{ Form::close() }}
<p>Not a member? <a href="/user/register">Register here</a>.</p>
@stop
If you browse to /auth/login in your browser you should see a simple login form.
To process the login, we’ll need to build a POST route. Authentication in Laravel is a breeze; we simply do this:
Route::post('/auth/login', function() {
$email = Input::get('email');
$password = Input::get('password');
if (Auth::attempt(array('email' => $email, 'password' => $password))) {
return Redirect::to('/')->with('success', 'You have been logged in');
}
else {
return Redirect::to('auth/login')->with('error', 'Login Failed');
}
return View::make('auth/login');
});
All the magic happens in Auth::attempt()
; if the login is successful, a session is created and a populated instance of the User
object is accessible via the static method Auth::user()
.
The logout method is equally straightforward:
Route::get('/auth/logout', function() {
Auth::logout();
return Redirect::to('/')->with('success', 'You have successfully logged out');
});
Basic Registration
Our last task in this part is to set up a basic registration process. Create a registration route inapp/routes.php
:
Route::get('/user/register', function() {
return View::make('user/register/index');
});
Now create the view app/views/user/register/index.blade.php
:
@extends('layouts.default')
@section('content')
{{ Form::open(array('url' => 'user/register')) }}
{{ Form::label('email', 'E-Mail Address') }}
{{ Form::text('email') }}
{{ $errors->first('email') }}
{{ Form::label('name', 'Your name') }}
{{ Form::text('name') }}
{{ $errors->first('name') }}
{{ Form::label('password', 'Password') }}
{{ Form::password('password') }}
{{ $errors->first('password') }}
{{ Form::label('password_confirmation', 'Repeat') }}
{{ Form::password('password_confirmation') }}
<div class="form-actions">
{{ Form::submit('Register', array('class' => 'btn btn-primary')) }}
</div>
{{ Form::close() }}
@stop
There are a couple of things to note here:
- The
$errors
object is automatically passed to the view, regardless of whether there are any errors in the submission. If the field’s okay, it’ll just print nothing. - By appending
_confirmation
to the name of a field, it’s simple to validate the two fields together; i.e. confirm that the user has re-entered the chosen password correctly.
Route::post('/user/register', function() {
$validator = Validator::make(
Input::all(),
array(
'name' => array('required', 'min:5'),
'email' => array('required', 'email', 'unique:users'),
'password' => array('required', 'confirmed')
)
);
if ($validator->passes()) {
$user = new User();
$user->name = Input::get('name');
$user->email = Input::get('email');
$user->password = Hash::make(Input::get('password'));
$user->save();
$role_pending = Role::where('name', '=', 'pending')->first();
$user->roles()->attach($role_pending);
Auth::login($user);
return Redirect::to('/')->with(
'success',
'Welcome to the site, . Auth::user()->name . '!'
);
}
else {
return Redirect::to('user/register')->with(
'error',
'Please correct the following errors:'
)->withErrors($validator);
}
});
This is all fairly basic stuff – we create a validator, passing it the POST variables with Input::all()
and a set of validation rules. If validation passes, we create a new user, assign them to the pending role, log them in, and then redirect them to the front page.
If validation fails, we redirect back to the form, create an error flash message, and pass the error messages from the validator – they’ll then be available in the view in the $errors
variable.
Conclusion
In this part we’ve gone step-by-step through building the bare bones of a subscription site. It has the ability to register a basic account, login, and logout. In the next part we’ll integrate Recurly for paid subscription plans.Frequently Asked Questions (FAQs) about Creating a Subscription-Based Website with Laravel and Recurly
What are the key differences between Laravel and other platforms for creating a subscription-based website?
Laravel is a PHP framework that offers a clean and elegant syntax, making it a joy to work with. It’s designed for developers who need a simple and elegant toolkit to create full-featured web applications. Laravel is different from other platforms because it comes with its own templating engine called Blade, which makes it easier to incorporate HTML and PHP in your views. It also has a robust ecosystem with tools like Laravel Mix for compiling assets and Laravel Echo for real-time broadcasting.
How does Recurly integrate with Laravel for subscription management?
Recurly is a subscription management platform that provides flexible subscription plans and pricing, automated communications, and advanced analytics. When integrated with Laravel, Recurly provides a seamless subscription management experience. Laravel communicates with Recurly’s API to manage subscriptions, process payments, and handle customer data.
Can I use other payment gateways with Laravel and Recurly?
Yes, Recurly supports over a dozen payment gateways, including PayPal, Stripe, and Braintree. You can choose the one that best suits your business needs. Laravel’s Cashier package also provides an expressive, fluent interface to various subscription billing services.
How secure is a Laravel and Recurly subscription website?
Laravel and Recurly both prioritize security. Laravel uses hashed and salted passwords, meaning your user’s passwords will be encrypted in the database. Recurly is PCI-DSS Level 1 compliant, the highest level of security a business can offer when it comes to protecting customers’ payment information.
How customizable is a Laravel and Recurly subscription website?
Laravel and Recurly offer a high level of customization. Laravel’s MVC architecture allows you to structure your website as you see fit, while Recurly’s flexible subscription plans and add-ons allow you to tailor your pricing and offerings to your business needs.
How scalable is a Laravel and Recurly subscription website?
Laravel and Recurly are both highly scalable. Laravel can handle large applications with complex back-end requirements, while Recurly can manage any number of subscribers and handle high volumes of transactions.
What kind of support is available for Laravel and Recurly?
Both Laravel and Recurly have strong communities and extensive documentation. Laravel has a dedicated forum and IRC channel, while Recurly offers support via email and live chat.
How does Laravel handle subscription renewals and cancellations?
Laravel’s Cashier package includes methods for handling subscription renewals and cancellations. You can easily check if a subscription is active, on trial, or cancelled, and handle these events accordingly.
Can I offer trial periods with a Laravel and Recurly subscription website?
Yes, both Laravel and Recurly support trial periods. You can set the length of the trial period when creating a subscription plan in Recurly, and Laravel’s Cashier package provides methods for handling trial periods.
How do I handle failed payments with Laravel and Recurly?
Recurly has a robust dunning management system for handling failed payments. Laravel’s Cashier package also includes methods for handling failed payments, allowing you to easily manage these situations.
Lukas is a freelance web and mobile developer based in Manchester in the North of England. He's been developing in PHP since moving away from those early days in web development of using all manner of tools such as Java Server Pages, classic ASP and XML data islands, along with JavaScript - back when it really was JavaScript and Netscape ruled the roost. When he's not developing websites and mobile applications and complaining that this was all fields, Lukas likes to cook all manner of World foods.