In this tutorial we’re going to see how easy it is to create a basic paid membership website using the Laravel Cashier package. You can see a demo of the app here and download the source code here.

Setting up a working environment

We need to create a boilreplate project to get started, and we can do that in two different ways:

  • we can clone the Github repo to our project folder.
  • Assuming that you have composer installed, ( check installation guide for more details).
    we run composer create-project laravel/laravel laravel_membership --prefer-dist, this will create a laravel boilerplate project in our laravel_membership folder.

Now we need to require the Laravel Cashier package to the project by adding "laravel/cashier": "~1.0" to our composer.json on the require section and run composer update to update our dependencies.

After that we need to tell our app to load the Cashier service provider class. We can do that by adding "Laravel\Cashier\CashierServiceProvider" to the providers array on the config/app.php file.

Note: You must run composer dump-autoload to update the classMap package.

Creating the database using migrations

If you are new to Laravel Migrations be sure to check the documentation.
We are going to use two tables:

posts table:
INT id
STRING title
LONG_TEXT content
BOOL is_premium

users table:
INT id
VARCHAR(60) email
VARCHAR(60) password

The Laravel artisan command line tool makes it easy to create and manage migration classes.

php artisan migrate:make create_posts_table --create="posts"

php artisan migrate:make create_users_table --create="users"

and then we fill the Schema::create callback function argument with the necessary code that looks like this

Schema::create('posts', function(Blueprint $table)
{
    $table->increments('id');
    $table->string('title');
    $table->longText('content');
    $table->boolean("is_premium");
    $table->timestamps();
});

Schema::create('users', function(Blueprint $table)
{
    $table->increments('id');
    $table->string('email', 100)->unique();
    $table->string('password', 60);
    $table->timestamps();
});

To let Laravel Cashier know about our billable table we need to create a specific migration for that. Laravel Cashier has a built in command for that purpose.

php artisan cashier:table users

Now we are ready to migrate our database

php artisan migrate

if you open your users table you will see a bunch of fields added when the package migration is executed.
stripe_active if you have an active subscription.
stripe_id user id on Stripe server.
stripe_plan Stripe subscription plan.
last_four credit card last four digits.
trial_ends_at an end date is stored if you specify a trial period.
subscription_ends_at subscription end date.

Now we will seed the database with some dummy data to get started; check the final result on GitHub.

Stripe billing process

Dealing with payment can be a pain in the neck, and Stripe can help you with that, they use tokens instead of card numbers etc.., and that’s how you can make sure that your customers stay secure while paying for your service.

NOTE: Check if Stripe is supported in your country, but you can still use it for testing if not.

To get started we need to get an account first. Stripe doesn’t have a monthly fee for the subscription, you only pay when you get paid.

Now, after getting an account you need to create Plans for your application (Monthly, Yearly, Silver, Gold…).

new_plan.png

Every field is self explanatory, so lets create a Gold membership that will cost $40 and a Basic membership for $10. They will be billed every month.

We have already added the necessary columns to our users table, now we need to let Laravel Cashier know that we will use the User class as our billing class.

use Laravel\Cashier\BillableInterface;
use Laravel\Cashier\BillableTrait;

class User extends Eloquent implements BillableInterface {

    use BillableTrait;
    protected $dates = ['trial_ends_at', 'subscription_ends_at'];

Note: we’re using BillableTrait and traits require PHP 5.4 or greater.

Now we have to set our Stripe API access key, which you can get from Your account > Account settings > API Keys and copy your Test Secret Key.

Settings API Keys

By using the BillableTrait we get access to the User::setStripeKey(key) method which can be called anywhere in our code, but the preferred way is to create a services.php file under your config directory and return an array like this:

return [
    'stripe' => [
            'secret'    => 'Your key'
        ]
];

When getStripeKey tries to load your key it will look for a property called stripeKey. If not found, it will automatically load your services file.

Creating our pages

To keep things simple we will create only a few pages:
– Signup: where user can signup with a membership plan ( Basic, Gold ).
– Login: members login page.
– Upgrade: upgrade from basic to gold membership.
– Post: display a single post page.

To speed up the process we will use bootsnipp. You can get the final code from the GitHub repo.

Login page:

login page

The login page has a basic email and password field, with a LoginController page that looks like this:

public function index(){
    return View::make('login');
}
public function store(){
    if( Auth::attempt( Input::only( ['email', 'password'] ), true)){
        return Redirect::to('/');
    }
    else{
        return Redirect::back()->withInput()->with( 'message', 'Email or password incorrect' );
    }
}
public function destroy(){
    Auth::logout();
    return Redirect::route("login");
}

Signup page:

signup page

The signup page has a Subscription plan field used to assign a user to plan.
We have also a Credit card number, Expiration date, CVC.

As we said earlier, we will never have to deal with any payment or verification process, we pass those values to the Stripe server to take care of the charging and verification process.
The return value is a token in case of success otherwise we get an error message that we can show to the user.

Let’s see what the front-end code looks like:

<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script>
    Stripe.setPublishableKey('Your public key');
    jQuery(function($) {
        $('#subscription-form').submit(function(event) {
            var $form = $(this);
            $form.find('button').prop('disabled', true);
            Stripe.card.createToken($form, stripeResponseHandler);

            return false;
        });
    });

    var stripeResponseHandler = function(status, response) {
        var $form = $('#subscription-form');

        if (response.error) {
            $form.find('.payment-errors').text(response.error.message);
            $form.find('button').prop('disabled', false);
        } else {
            var token = response.id;
            $form.append($('<input type="hidden" name="stripeToken" />').val(token));
            $form.get(0).submit();
        }
    };
</script>

First we include the JavaScript API file, then we set our public key that we grabbed from our Stripe dashboard settings.

Next we attach a callback function to our submit form (be sure that your form ID matches the one used on the event handler), to prevent a double submission we disable our submit button.
The Stripe.card.createToken accepts two arguments, the first one is a JSON object that has some required and optional values.

Required values:

  • number: card number as a string without any separators.
  • exp_month: two digit number representing the card’s expiration month.
  • exp_year: two or four digit number representing the card’s expiration year.

Optional values:

  • cvc: card security code as a string, the cvc number is optional but recommended to help prevent fraud.
  • name: cardholder name.
  • address_line1: billing address line 1.
  • address_line2: billing address line 2.
  • address_city: billing address city.
  • address_state: billing address state.
  • address_zip: billing zip as a string.
  • address_country: billing address country.
    You can notice that we’re passing a form object instead of a JSON object, you can choose to grab the values manually or use the data-stripe html5 attribute on your inputs and Stripe will use some helper methods to grab those values automatically for you. Ex:
    <input data-stripe="number" type="text">

The second argument passed to Stripe.card.createToken method is a callback function to handle the response.

In case of failure the stripeResponseHandler will try to find an element with a class of payment_errors to display some descriptive errors to the user.
In case of success a stripeToken hidden input will be appended to the form and it will be available on submit.

Additional options

  • Trial periods: as we stated before, when you create a new plan you have a choice to specify a trial period for users to test your product, and they won’t be charged until the specified period has elapsed.
  • Coupons: you create coupons via your dashboard menu where you can specify a fixed amount or by percentage,with some other useful options.

Now let’s move to our SignupController to see how we will handle this.

public function store(){
    $user = new User;
    $user->email = Input::get( 'email' );
    $user->username = Input::get( 'username' );
    $user->password = Hash::make( Input::get( 'password' ) );
    $user->save();
    $user->subscription(Input::get( 'subscription' ))->create( Input::get( 'stripeToken' ) );

    return 'you are now registred';
}

We will skip the validation process to keep things simple.

After creating a new User and saving it, we now have the option to subscribe the user to a new membership plan. The subscription method accepts an already registered plan as an argument, that can be either a PlanInterface or a String and return a StripeGateway.
The create method accepts a token as a parameter; we pass the new hidden input value with the name stripeToken.

Upgrade page:

upgrade page

The upgrade page will submit to the UpgradeController that looks like this:

public function store(){
    if( !Auth::check() )
        return Redirect::route("login");

    Auth::user()->subscription('gold')->swap();

    return 'You are now a GOLD member';
}

We check if the user is logged in first, then we create a new subscription with the new plan and we call the swap method, obviously in a real project you will have some fees adjustments and a downgrade option, but it should work the same way.

Post page:

Post page

The PostController checks if the post is_premium, and if so, we test if the user is a gold member who can see the post, else we return a simple error message.

public function show( $id ){
    $post = Post::find( $id );

    if( $post->is_premium && Auth::user()->stripe_plan != 'gold' )
        return View::make('error', [ 'message' => 'Only GOLD members can read this post, <a href="/upgrade">upgrade</a> your membership to get access' ] );

    return View::make('post', [ 'post' => $post ] );
}//show

Of course in our routes.php file we need to add an auth filter to prevent unauthenticated users from accessing the page.
Our routes file will look like this:

Route::get('/', function()
{
    $posts = Post::all();

    return View::make('index', [ 'posts' => $posts ]);
})->before('auth');


Route::get('/post/{id}', [ 'as' => 'post', 'uses' => 'PostsController@show' ])->before('auth');
Route::resource('login', 'LoginController', [ 'only' => [ 'index', 'store', 'destroy' ] ]);
Route::resource('signup', 'SignupController', [ 'only' => [ 'index', 'store' ] ]);
Route::resource('upgrade', 'UpgradeController', [ 'only' => [ 'index', 'store' ] ]);

Other useful methods

  • withCoupon: we said before that we have the possibility to create discount coupons, in our example we can do that like so:
$user->subscription(Input::get( 'subscription' ))->withCoupon('coupon code')->create( Input::get( 'stripeToken' ) );
  • cancel: you can easily cancel a subscription using this method, but you have to check whether the user is onGracePeriod to be sure you don’t block them immediately:
User::find(1)->onGracePeriod();
  • onPlan: see if a user is on a certain plan.
  • onTrial: see if a user still on trial period.
  • canceled: if the user has canceled their subscription.
  • getLastFourCardDigits: get the user card last four digits.
  • getSubscriptionEndDate: get the subscription end date.
  • getTrialEndDate: get the trial end date.
  • invoices: get the list of user invoices.
  • findInvoice: find an invoice by id.
  • downloadInvoice: generate a downloadable invoice by id.

Conclusion

In this tutorial we explored how Laravel Cashier can ease the billing process and help manage your customers more easily.

We certainly didn’t cover everything here, but it’s a start for you to dig into the source code to explore what else you can do. If you’d like to see a Nitrous-hosted demo of this app, see here.

Frequently Asked Questions (FAQs) about Laravel Cashier Memberships

How does Laravel Cashier handle subscription billing?

Laravel Cashier is a powerful tool that simplifies the process of managing subscription billing. It provides an expressive, fluent interface to Stripe’s and Braintree’s subscription billing services. It handles almost all of the boilerplate subscription billing code you are dreading writing. Laravel Cashier can handle coupons, swapping subscription, subscription “quantities”, cancellation grace periods, and even generate invoice PDFs.

What are the prerequisites for using Laravel Cashier?

Before using Cashier, you’ll need to create a Stripe account and install the Cashier package via Composer. You’ll also need to add the Billable trait to your model definition – this trait provides various methods to allow you to perform common billing tasks, such as creating subscriptions, applying coupons, and updating card information.

How can I integrate Laravel Cashier into my existing Laravel project?

Integrating Laravel Cashier into your existing Laravel project is straightforward. First, you need to install the package via Composer. Then, you need to prepare the database by running a migration that adds several columns to your users table. After that, you need to set up the API keys and add the Billable trait to your model. Finally, you can use the various methods provided by the Billable trait to manage your subscriptions.

How does Laravel Cashier handle failed payments?

Laravel Cashier has built-in functionality to handle failed payments. If a payment fails, Cashier will automatically retry the payment. If the payment continues to fail, the subscription will be cancelled. However, you can also handle these events manually by listening for the relevant events and taking appropriate action.

Can I use Laravel Cashier with other payment gateways?

Currently, Laravel Cashier only supports Stripe and Braintree. However, you can extend Cashier to work with other payment gateways if you wish. This would require a good understanding of both Laravel and the payment gateway’s API.

How can I cancel a subscription using Laravel Cashier?

You can cancel a subscription using the cancel method on a user’s subscription. When a subscription is cancelled, Cashier will automatically set the end date of the subscription to the end of the current billing cycle.

Can I offer trial periods with Laravel Cashier?

Yes, Laravel Cashier supports trial periods. You can set the trial period when creating the subscription. The user will not be billed until the trial period is over.

How can I generate invoices using Laravel Cashier?

Laravel Cashier can automatically generate invoice PDFs. You can access the downloadable PDF version of the invoice by calling the download method on an invoice instance, which will write the PDF to disk or stream it to the browser.

Can I apply coupons to subscriptions using Laravel Cashier?

Yes, Laravel Cashier supports applying coupons to subscriptions. You can do this by using the withCoupon method when creating a new subscription.

How can I handle subscription quantities with Laravel Cashier?

Laravel Cashier allows you to handle subscription quantities, which can be useful for “per seat” type subscriptions. You can update the quantity of a subscription using the updateQuantity method on a subscription instance.

Younes RafieYounes Rafie
View Author

Younes is a freelance web developer, technical writer and a blogger from Morocco. He's worked with JAVA, J2EE, JavaScript, etc., but his language of choice is PHP. You can learn more about him on his website.

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