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

Lukas White
Share

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.