Creating a Subscription-Based Website with Laravel and Recurly, Part 2

Share this article

In the first part of this series we created the bare bones of a subscription-based website using Laravel. We also set the site up with roles, and granted a pending role to new users. We’re now at the point where we can perform basic registration, login, and logout. In this second part, we’ll integrate Recurly to set up our paid membership plans.

Getting Started

There are two Recurly libraries – the PHP client library, which we installed via Composer at the beginning of the first part of the series – and Recurly JS. Recurly JS is te client-side library for dynamically integrating forms that securely handle card information. That card information is POSTed to Recurly’s servers, not our web application, making compliance headaches significantly smaller. Download the Recurly JS library and copy recurly.min.js from the build folder into public/js/libs, and add it to your layout before the closing tag:
<script src="/js/libs/recurly.min.js"></script>
</body>
We’ll also need the CSS styles which are used when displaying the payment form. Create the directory css/recurly and copy the themes directory into it, and then refer to it in the relevant section of your layout:
<link href="/css/recurly/themes/default/recurly.css" rel="stylesheet">
When you first login after creating an account with Recurly, you’re given the opportunity to create your subscription plans. In the first part we created three levels: Bronze, Silver, and Gold. You can always change them or add more later, but there’s no harm in doing them now. Create a plan called Bronze; ensuring that the plan code is set to “bronze” (all lower-case). Set a price – I’ve set it for £4.99 per month, but you’re free to select any amount and/or timeframe you like. You can optionally set a one-off setup charge or even set a free trial period. Repeat the process twice more, setting up Silver (plan code: “silver”) and Gold (plan code: “gold”) plans – I’ve set mine at £9.99 and £14.99 per month respectively. Now go to the Recurly admin panel where there are a few things we need to set up, as well as find the relevant information for your application’s configuration. Click API access on the left-hand side. Click Enable API access. Take a note of your API key, and your subdomain. Now go to Recurly.js on the left hand side (under Developer). Click Enable Recurly.js and Transparent Post API. Take a note of your private key. Now, create a Recurly configuration file in app/config/recurly.php, replacing the values with the ones you’ve just noted down along with the three-digit code that represents your chosen default currency (e.g. USD, GBP, AUD):
<?php
return array(
    'api_key'          => 'YOUR-API-KEY',
    'private_key'      => 'YOUR-PRIVATE-KEY',
    'subdomain'        => 'YOUR-SUBDOMAIN',
    'default_currency' => 'XYZ'
);
Now go to Push Notifications on the left hand side and click Configure. Enter the URL to your application with /recurly appended to it, for example: http://www.example.com/recurly. Leave the HTTP Auth Username and HTTP Auth Password fields blank for now.

The Signup Page

Now let’s create a signup page that allows a potential customer to select their plan. Create app/views/home/signup.blade.php:
@extends('layouts.default')

@section('content')

  <h1>Signup</h1>

  <p>Please select your plan...</p>

  <div class="row-fluid pricing-table pricing-three-column">
    <div class="span4 plan">
      <div class="plan-name-bronze">
      <h2>Bronze</h2>
      <span>&pound;4.99 / Month</span>
      </div>
      <ul>
      <li class="plan-feature">Feature #1</li>
      <li class="plan-feature">Feature #2</li>
      <li class="plan-feature"><a href="/user/register/bronze" class="btn btn-primary btn-plan-select"><i class="icon-white icon-ok"></i> Select</a></li>
      </ul>
    </div>
    <div class="span4 plan">
      <div class="plan-name-silver">
      <h2>Silver <span class="badge badge-warning">Popular</span></h2>
      <span>&pound;9.99 / Month</span>
      </div>
      <ul>
      <li class="plan-feature">Feature #1</li>
      <li class="plan-feature">Feature #2</li>
      <li class="plan-feature"><a href="/user/register/silver" class="btn btn-primary btn-plan-select"><i class="icon-white icon-ok"></i> Select</a></li>
      </ul>
    </div>
    <div class="span4 plan">
      <div class="plan-name-gold">
      <h2>Gold</h2>
      <span>&pound;4.99 / Month</span>
      </div>
      <ul>
      <li class="plan-feature">Feature #1</li>
      <li class="plan-feature">Feature #2</li>
      <li class="plan-feature"><a href="/user/register/gold" class="btn btn-primary btn-plan-select"><i class="icon-white icon-ok"></i> Select</a></li>
      </ul>
    </div>
    </div>

@stop
Of course, if you wanted to ensure the prices were always up-to-date, you could pull in the plan details via the Recurly API and populate them in the template dynamically. Now add the following to css/style.css:
.pricing-table .plan {
    background-color: #f3f3f3;
    text-align: center;
}

.plan:hover {
    background-color: #fff;
}

.plan {
    color: #fff;
    background-color: #5e5f59;
    padding: 20px;
}
  
.plan-name-bronze {
    background-color: #665D1E;
    color: #fff;
    padding: 20px;
}
  
.plan-name-silver {
    background-color: #C0C0C0;
    color: #fff;
    padding: 20px;
}
  
.plan-name-gold {
    background-color: #FFD700;
    color: #fff;
    padding: 20px;
} 
  
.pricing-table-bronze  {
    background-color: #f89406;
    color: #fff;
    padding: 20px;
}
  
.pricing-table .plan .plan-name span {
    font-size: 20px;
}
 
.pricing-table .plan ul {
    list-style: none;
    margin: 0;
}
 
.pricing-table .plan ul li.plan-feature {
    border-top: 1px solid #c5c8c0;
    padding: 15px 10px;
}
 
.pricing-three-column {
    margin: 0 auto;
    width: 80%;
}
 
.pricing-variable-height .plan {
    display: inline-block;
    float: none;
    margin-left: 2%;
    vertical-align: bottom;
    zoom:1;
    *display:inline;
}
 
.plan-mouseover .plan-name {
    background-color: #4e9a06 !important;
}
 
.btn-plan-select {
    font-size: 18px;
    padding: 8px 25px;
}
And finally, create the route in app/routes.php
:
Route::get('/signup', function() {
    return View::make('home/signup');
});

Taking Payments

Now for the next stage of the registration page – taking payments. First, let’s modify the user/register POST callback; instead of redirecting to the homepage we’ll put the user in the session and redirect to the payment page. Change the following:
return Redirect::to('/')->with(
    'success',
    'Welcome to the site, . Auth::user()->name . '!'
);
to:
Session::put('register_user', $user);
return Redirect::to('/user/register/payment');
We need to expand the default layout so that we can embed the JavaScript code in the footer. Add the following line after the last script tag:
@yield('scripts')
Now, create a new route:
Route::get('/user/register/payment', function() {
    Recurly_js::$privateKey = Config::get('recurly.private_key');
    $plan = 'bronze'; // todo: get this from vars
    $user = Session::get('register_user');
    $signature = Recurly_js::sign(array(
        'account'      => array(
            'account_code' => 'user_' . $user->id
        ),
        'subscription' => array(
            'plan_code'    =>  $plan,
            'currency'     => Config::get('recurly.default_currency')
        )
    ));

    return View::make('user/register')->with(array(
        'plan'      => $plan,
        'subdomain' => Config::get('recurly.subdomain'),
        'currency'  => Config::get('recurly.default_currency'),
        'signature' => $signature
    ));
});
A few notes on this code:
  • We need to set the private key on the Recurly_js class, which we’ll take from the configuration file we created earlier.
  • The plan should get carried over from the previous stage in the registration process; I’ve left this implementation piece as a user exercise.
  • We need to generate a signature for Recurly.js, using several pieces of information. Amongst them, is an identifier for the user in question, which we create by concatenating the class (user) and the user’s ID.
  • This signature is passed along with some other required information to the view.
The payment page view comes in two parts. First, the HTML which is is extremely simple:
@extends('layouts.default')

@section('content')

  <div id="recurly-subscribe">
  </div>

@stop
Recurly.js will inject its client-side generated payment form into this div. Next we add some JavaScript to the view, in a section that will get output at the foot of the layout template:
@section('scripts')
<script>
Recurly.config({
    subdomain: '{{ $subdomain }}',
    currency: '{{ $currency }}'
});

Recurly.buildSubscriptionForm({
    target: '#recurly-subscribe',
    // Signature must be generated server-side with a utility
    // method provided in client libraries.
    signature: '{{ $signature }}',
    successURL: '/user/register/confirm',
    planCode: '{{ $plan }}',
    distinguishContactFromBillingInfo: true,
    collectCompany: false,
    termsOfServiceURL: 'http://www.example.com/terms',
    acceptPaypal: true,
    acceptedCards: ['mastercard',
                    'discover',
                    'american_express', 
                    'visa'],
    account: {
        firstName: 'Joe',
        lastName: 'User',
        email: 'test@example.net',
        phone: '555-555-5555'
    },
    billingInfo: {
        firstName: 'Joe',
        lastName: 'User',
        address1: '123 somestreet',
        address2: '45',
        city: 'San Francisco',
        zip: '94107',
        state: 'CA',
        country: 'US',
        cardNumber: '4111-1111-1111-1111',
        CVV: '123'
    }
});
</script>
@stop
This is where the magic happens – Recurly builds the payment form and injects it into the div with ID #recurly-subscribe, which requires some of the information we’ve passed to the view, along with the server-generated signature. Next up, the callback which Recurly POSTs back to upon successful submission of the form which is defined in the successURL parameter above:
Route::post('/user/register/confirm', function() {
    $recurly_token = Input::get('recurly_token');
    Recurly_js::$privateKey = Config::get('recurly.private_key');
    $result = Recurly_js::fetch($recurly_token);
    var_dump($result);
});
Again, we initialise Recurly.js with the private key from the config, and use it to fetch the object represented by the token that Recurly sends as a POST variable (recurly_token). It’ll be an instance of Recurly_Subscription, from which we can extract various bits of information. I’ve outputted it with var_dump() for you to take a look at. Let’s first get the plan code, so we know what subscription plan the user has just signed up for:
$plan_code = $result->plan->plan_code;
Now find the corresponding role; notice we’ve just given them the same name (e.g. “bronze”, “silver” and “gold”).
$role = Role::where('name', '=', $plan_code)->first();
Then get the user from the session (and then remove it from the session):
$user = Session::get('register_user');
Session::forget('register_user');
Then we grant the appropriate role to the new user:
$user->roles()->attach($role);
We then need to remove the pending role:
$role_pending = $role_pending = Role::where('name', '=', 'pending')->first();
DB::table('role_user')->where('user_id', '=', $user->id)->where('role_id', '=', $role_pending->id)->delete();
We’ve now enhanced the registration process to take payments, create subscriptions, and apply roles to new user accounts based on the selected plan. In the next part we’ll look at further management of user accounts and subscriptions.

Account Management Pages

Let’s create a simple page from which users will be able to manage their account. Obviously it’ll require that the user be logged in, so we’ll need to apply the auth filter. Rather than specify this filter for every protected page, we can put all the relevant routes into a group, like so:
Route::group(array('before' => 'auth'), function() {
    Route::get('/user/account', function() {
        // User must be logged in
    });
    Route::get('user/account/billing', function() {
        // User must be logged in
    });
});
The account page callback is straightforward:
Route::get('/user/account', function() {
    return View::make('user/account/index');
});
Now address the view app/views/user/account/index.blade.php:
@extends('layouts.default')

@section('content')

    <h1>Your Account</h1>

    <ul>
	<li><a href="/user/account/edit">Edit your account information</a></li>
	<li><a href="/user/account/plan">Update your subscription plan</a></li>
    	<li><a href="/user/account/billing">Update your Billing information</a></li>
    </ul>

@stop
Let’s start with the update billing information page. Of course, we don’t store people’s billing information, so like the payment page, Recurly will create and populate the form for you, POSTing the new information back to Recurly without it ever going near your web application.
Route::get('user/account/billing', function() {
    Recurly_js::$privateKey = Config::get('recurly.private_key');

    $account_code = 'user_' . Auth::user()->id;

    $signature = Recurly_js::sign(
        array('account' => array('account_code' => $account_code))
    );

    return View::make('user/account/billing')->with(array(      
        'subdomain'     => Config::get('recurly.subdomain'),
        'currency'      => Config::get('recurly.default_currency'),
        'account_code'  => $account_code,
        'signature'     => $signature
    ));
});
This is very similar to the payment page in that we’re initialising the Recurly.js library, creating a signature (albeit using different information), and passing a few parameters to the view. The view app/views/user/account/billing.blade.php follows pretty much the same lines as the payment page:
@extends('layouts.default')

@section('content')

 <div id="recurly-billing">
 </div>

@stop

@section('scripts')
<script>
Recurly.config({
    subdomain: '{{ $subdomain }}',
    currency: '{{ $currency }}'
});

Recurly.buildBillingInfoUpdateForm({
    target: '#recurly-billing',
    successURL: '/user/account/billing/confirm',
    accountCode: '{{ $account_code }}',
    signature: '{{ $signature }}'
});
</script>
@stop
Finally, a very simple callback for when a user has submitted the billing info form:
Route::post('user/account/billing/confirm', function()
{
    return Redirect::to('/user/account')->with('success', 'Your billing information has been updated.');
});
And that’s it – users can now update their billing information! I haven’t implemented the edit account functionality here, but it’s pretty straightforward. I’ll leave it as an exercise for you.

Push Notifications

In addition to being able to query Recurly’s API, the service can “ping” your application with a notification when one of a number of events occur. These push notifications shouldn’t be confused with the mobile variety, however – they’re completely different. Essentially the service sends a POST request to a URI you specify, and sends an XML document as the request’s body. You can use the Recurly libraries to extract the relevant information, and act accordingly. These push notifications are detailed in the Recurly documentation. Let’s define our callback, for when a push notification has been received.
Route::post('recurly', function(){
    $xml = file_get_contents ("php://input");
    $notification = new Recurly_PushNotification($xml);

    switch ($notification->type) {
        // ... process notification
    }
});
You’ve probably realised by now that once someone has signed up and paid for the service, they remain a member indefinitely whether their subscription continues or not. So, the notification we’re most interested in for the purposes of this tutorial is the Canceled Subscription Notification. We can use the notification from Recurly to identify inactive subscriptions and revoke the corresponding roles on the account. For example:
switch ($notification->type) {
    case 'canceled_subscription_notification':
        // get the account code
        $account_code = $notification->account->account_code;
        // extract the user ID (account_code format is user_ID)
        $user_id = intval(substr($account_code, (strpos($account_code, '_')+1)));   
        // find the user in question
        $user = User::find($user_id);
        // get the plan code
        $plan_code = $notification->subscription->plan->plan_code;
        // find the corresponding role...
        $role = Role::where('name', '=', $plan_code)->first();
        // ...and revoke it
        DB::table('role_user')->where('user_id', '=', $user->id)->where('role_id', '=', $role)->delete();
        break;

    // ... process notification
}
There are a number of other things you could do, depending on the type of notification. You could use subscription-based notifications (new, renewed, canceled, etc.) to create a subscription history, keep track of the number of members, and analyze cancellations. You could also use transactions – whether positive (payments) or negative (refunds) – to keep track of actual and expected revenue.

Frequently Asked Questions about Creating a Subscription-Based Website with Laravel and Recurly

How can I integrate Recurly with Laravel for my subscription-based website?

Integrating Recurly with Laravel involves several steps. First, you need to install the Recurly client library for PHP using Composer. Then, you need to configure your Recurly account settings in your Laravel application. This includes setting up your API keys and configuring your subscription plans. Once this is done, you can use the Recurly API to create, update, and manage subscriptions in your Laravel application.

What are the benefits of using Laravel and Recurly for my subscription-based website?

Laravel and Recurly offer several benefits for subscription-based websites. Laravel is a powerful and flexible PHP framework that makes it easy to build complex web applications. It offers features like routing, authentication, sessions, caching, and more. Recurly, on the other hand, is a subscription management platform that provides features like flexible billing, revenue recognition, and analytics. By integrating Recurly with Laravel, you can leverage the strengths of both platforms to create a robust subscription-based website.

Can I offer different subscription plans with Laravel and Recurly?

Yes, you can offer different subscription plans with Laravel and Recurly. Recurly allows you to create and manage multiple subscription plans with different pricing and billing cycles. You can then use the Recurly API in your Laravel application to allow users to choose and subscribe to these plans.

How can I handle subscription renewals with Laravel and Recurly?

Recurly automatically handles subscription renewals for you. When a subscription is due for renewal, Recurly will attempt to charge the customer’s payment method on file. If the charge is successful, the subscription is renewed and Recurly sends a renewal invoice to the customer. You can use webhooks in your Laravel application to receive notifications about subscription renewals and take appropriate actions.

How can I manage failed payments with Laravel and Recurly?

Recurly provides a comprehensive dunning management system to handle failed payments. When a payment fails, Recurly will automatically send email reminders to the customer and attempt to charge the payment method again at configurable intervals. You can customize the dunning schedule and email templates to suit your needs. In your Laravel application, you can use webhooks to receive notifications about failed payments and take appropriate actions.

Can I offer free trials with Laravel and Recurly?

Yes, you can offer free trials with Laravel and Recurly. Recurly allows you to set up subscription plans with a free trial period. During this period, the customer is not charged. Once the trial period ends, Recurly will automatically start billing the customer. You can use the Recurly API in your Laravel application to manage free trials.

How can I handle cancellations with Laravel and Recurly?

Recurly provides several options to handle cancellations. You can allow customers to cancel their subscription immediately, at the end of their current billing cycle, or at a future date. You can use the Recurly API in your Laravel application to implement these cancellation options.

Can I offer discounts with Laravel and Recurly?

Yes, you can offer discounts with Laravel and Recurly. Recurly allows you to create and manage discount coupons that can be applied to subscriptions. You can set up coupons with a fixed amount or percentage discount, and specify the duration of the discount. You can use the Recurly API in your Laravel application to manage coupons and apply them to subscriptions.

How can I handle refunds with Laravel and Recurly?

Recurly provides a flexible refund system that allows you to issue full or partial refunds for subscription payments. You can issue refunds directly from your Recurly dashboard or use the Recurly API in your Laravel application to issue refunds programmatically.

Can I use Laravel and Recurly for a multi-tenant subscription-based website?

Yes, you can use Laravel and Recurly for a multi-tenant subscription-based website. Laravel provides features like route model binding and middleware that make it easy to build multi-tenant applications. Recurly allows you to manage subscriptions for multiple tenants from a single account. You can use the Recurly API in your Laravel application to manage subscriptions for each tenant.

Lukas WhiteLukas White
View Author

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.

Intermediate
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week