w3resource

Laravel (5.7) Laravel Cashier

Introduction

Laravel Cashier provides us with a fluent, expressive interface to Braintree's and Stripe's subscription billing services.

Cashier handles almost all of the boilerplate code that you dread to write. Cashier will help you handle coupons, swapping subscription, subscription "quantities", cancellation grace periods, and even handles generation of PDFs for invoice.

Upgrading Cashier

Whenever you are upgrading to a new major version of the Cashier, carefully reviewing the upgrade guide is very important.

Configuration

Stripe

Composer

The first thing is to add the Cashier package for Stripe to your dependencies:

composer require laravel/cashier

Database Migrations

Before using Cashier, you'll also need to prepare the database. you need to add several columns to your users table and create a new subscriptions table to hold all of your customer's subscriptions:

Schema::table('users', function ($table) {
    $table->string('stripe_id')->nullable()->collation('utf8mb4_bin');
    $table->string('card_brand')->nullable();
    $table->string('card_last_four', 4)->nullable();
    $table->timestamp('trial_ends_at')->nullable();
});

Schema::create('subscriptions', function ($table) {
    $table->increments('id');
    $table->unsignedInteger('user_id');
    $table->string('name');
    $table->string('stripe_id')->collation('utf8mb4_bin');
    $table->string('stripe_plan');
    $table->integer('quantity');
    $table->timestamp('trial_ends_at')->nullable();
    $table->timestamp('ends_at')->nullable();
    $table->timestamps();
});

Once you have created these migrations, you can then run the migrate Artisan command.

Billable Model

'

The next thing is to add the Billable trait to your model definition. This trait will provide various methods that allows you to perform common billing tasks, such as creating subscriptions, applying coupons, and updating credit card information:

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

API Keys

The finally thing you should do, is to configure your Stripe key in your services.php configuration file. You can also retrieve your Stripe API keys from the Stripe control panel:

'stripe' => [
    'model'  => App\User::class,
    'key' => env('STRIPE_KEY'),
    'secret' => env('STRIPE_SECRET'),
],

Braintree

Braintree Caveats

For many of the operations you want, the Stripe and Braintree implementations of Cashier will function the same. Both of these services provide subscription billing with credit cards but Braintree supports payments via PayPal. However, Braintree also lacks some of the features that are supported by Stripe. You should however keep the following in mind when deciding to use Stripe or Braintree:

  • Braintree has support for PayPal while Stripe does not.
  • Braintree doesn't have support for increment and decrement methods on subscriptions. This is a Braintree limitation, not a Cashier limitation.
  • Braintree does not have support for percentage based discounts. This is a Braintree limitation, not a Cashier limitation.

Composer

First, you should add the Cashier package for Braintree to your dependencies:

composer require "laravel/cashier-braintree":"~2.0

Plan Credit Coupon

Before you use Cashier with Braintree, you need to define a plan-credit discount in your Braintree control panel. This discount is used to properly prorate subscriptions that change from monthly to yearly billing, or from yearly to monthly billing.

The discount amount configured in the Braintree control panel can be set to any value you wish, as Cashier overrides the defined amount with our own custom amount each time we apply the coupon. This coupon is necessary since Braintree does not natively support prorating subscriptions across subscription frequencies.

Database Migrations

Before you use Cashier, we will need to prepare the database. You need to add several columns to your users table and create a new subscriptions table to hold all of your customer's subscriptions:

Schema::table('users', function ($table) {
    $table->string('braintree_id')->nullable();
    $table->string('paypal_email')->nullable();
    $table->string('card_brand')->nullable();
    $table->string('card_last_four')->nullable();
    $table->timestamp('trial_ends_at')->nullable();
});

Schema::create('subscriptions', function ($table) {
    $table->increments('id');
    $table->unsignedInteger('user_id');
    $table->string('name');
    $table->string('braintree_id');
    $table->string('braintree_plan');
    $table->integer('quantity');
    $table->timestamp('trial_ends_at')->nullable();
    $table->timestamp('ends_at')->nullable();
    $table->timestamps();
});

Once you have created the migrations, you should run the migrate Artisan command.

Billable Model

Next thing is to add the Billable trait to your model definition:

use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
}

API Keys

Next, you need to configure the following options in your services.php file:

'braintree' => [
    'model'  => App\User::class,
    'environment' => env('BRAINTREE_ENV'),
    'merchant_id' => env('BRAINTREE_MERCHANT_ID'),
    'public_key' => env('BRAINTREE_PUBLIC_KEY'),
    'private_key' => env('BRAINTREE_PRIVATE_KEY'),
],

You should then add the following Braintree SDK calls to your AppServiceProvider service provider's boot method:

\Braintree_Configuration::environment(config('services.braintree.environment'));
\Braintree_Configuration::merchantId(config('services.braintree.merchant_id'));
\Braintree_Configuration::publicKey(config('services.braintree.public_key'));
\Braintree_Configuration::privateKey(config('services.braintree.private_key'));

Currency Configuration

The default currency for Cashier is United States Dollars (USD). You can however change the default currency by calling the Cashier::useCurrency method from within the boot method of one of your service providers. The useCurrency method will accept two string parameters: the currency and the currency's symbol:

use Laravel\Cashier\Cashier;
Cashier::useCurrency('eur', '€');

Subscriptions

Creating Subscriptions

To create a subscription, you need to first retrieve an instance of your billable model, which typically is an instance of App\User. Once the model instance has been retrieved, you can use the newSubscription method to create the model's subscription:

$user = User::find(1);
$user->newSubscription('main', 'premium')->create($stripeToken);

The first argument passed to the newSubscription method has to be the name of the subscription. If the application you are building only offers a single subscription, you can call this main or primary. The second argument will be the specific Stripe / Braintree plan the user is subscribing to. This value has to correspond to the plan's identifier in Stripe or Braintree.

A Stripe credit card / source token is accepted by the create method, begins the subscription as well as updates your database with the customer ID and other relevant billing information.

Additional User Details

If you would prefer to specify additional customer details, you can do so by passing them as the second argument to the create method:

$user->newSubscription('main', 'monthly')->create($stripeToken, [
    'email' => $email,
]);

To learn more about the additional fields supported by Stripe or Braintree, check out Stripe's documentation on customer creation or the corresponding Braintree documentation.

Check the Stripe or Braintree documentation on customer creation to learn more about the additional fields supported by both services.

Coupons

If you would want to apply a coupon when creating the subscription, you can use the withCoupon method:

$user->newSubscription('main', 'monthly')
     ->withCoupon('code')
     ->create($stripeToken);

Checking Subscription Status

Once a user has subscribed to your application, you can easily check their subscription status using a variety of convenient methods. First, the subscribed method will return true if the user has an active subscription, even if the subscription is currently in its trial period:

if ($user->subscribed('main')) {
    //
}

The subscribed method will also make a great candidate for a route middleware, this allows you to filter access to routes and controllers based on the user's subscription status:

public function handle($request, Closure $next)
{
    if ($request->user() && ! $request->user()->subscribed('main')) {
        // This user is not a paying customer...
        return redirect('billing');
    }

    return $next($request);
}

If you want to determine if a user is still within their trial period, you can use the onTrial method. This method is used to display a warning to the user that they are still on their trial period:

if ($user->subscription('main')->onTrial()) {
    //
}

The subscribedToPlan method can be used to determine if the user is subscribed to a given plan based on a given Stripe / Braintree plan ID. We will determine in example below, if the user's main subscription is actively subscribed to the monthly plan:

if ($user->subscribedToPlan('monthly', 'main')) {
    //
}

Cancelled Subscription Status

If you want to determine if the user was once an active subscriber, but cancelled their subscription, you can use the cancelled method:

if ($user->subscription('main')->cancelled()) {
    //
}

You can also determine if a user has cancelled his subscription, but is still on his "grace period" until the subscription fully expires. For instance, consider a user who cancels a subscription on March 5th that was originally scheduled to expire on March 10th, the user is on his "grace period" until March 10th. Note that the subscribed method will still return true during this time:

if ($user->subscription('main')->onGracePeriod()) {
    //
}

Changing Plans

After a user subscribes to your application, they may occasionally want to move or change to a new subscription plan. If you want to swap a user to a new subscription, you should pass the plan's identifier to the swap method:

$user = App\User::find(1);
$user->subscription('main')->swap('provider-plan-id');

In the case where the user is on trial, the trial period is maintained. Additionally, if the subscription has a "quantity", that quantity is also maintained.

If you want to swap plans and cancel any trial period that the user is currently on, you can use the skipTrial method:

$user->subscription('main')
        ->skipTrial()
        ->swap('provider-plan-id');

Subscription Quantity

Sometimes the subscriptions are affected by "quantity". For instance, your application may be charging $10 per month per user on an account. If you want to easily increment or decrement your subscription quantity, you should use the incrementQuantity and decrementQuantity methods:

$user = User::find(1);
$user->subscription('main')->incrementQuantity();

// Adds five to the subscription's current quantity...
$user->subscription('main')->incrementQuantity(5);

$user->subscription('main')->decrementQuantity();

// Subtracts five to the subscription's current quantity...
$user->subscription('main')->decrementQuantity(5);

Alternatively, you can set a specific quantity using the updateQuantity method:

$user->subscription('main')->updateQuantity(10);

The noProrate method can be used to update the subscription's quantity without pro-rating the charges:

$user->subscription('main')->noProrate()->updateQuantity(10);

Subscription Taxes

To specify the tax percentage that a user pays on a subscription, implement the taxPercentage method on your billable model, and then return a numeric value between 0 and 100, with no more than 2 decimal places.

public function taxPercentage() {
    return 20;
}

The taxPercentage method will enable you to apply a tax rate on a model-by-model basis, which can be helpful for a user base that spans multiple countries and tax rates.

Syncing Tax Percentages

When changing the hard-coded value that is returned by the taxPercentage method, the tax settings on any existing subscriptions for the user remains the same. If you want to update the tax value for existing subscriptions with the returned taxPercentage value, call the syncTaxPercentage method on the user's subscription instance:

$user->subscription('main')->syncTaxPercentage();

Subscription Anchor Date

By default, the billing cycle anchor is the date that the subscription was created, or if you use a trial period, the date that the will trial end. If you want to modify the billing anchor date, you may use the anchorBillingCycleOn method:

use App\User;
use Carbon\Carbon;

$user = User::find(1);

$anchor = Carbon::parse('first day of next month');

$user->newSubscription('main', 'premium')
            ->anchorBillingCycleOn($anchor->startOfDay())
            ->create($stripeToken);

Cancelling Subscriptions

If you want to cancel a subscription, you should call the cancel method on the user's subscription:

$user->subscription('main')->cancel();

When you cancel a subscription, Cashier automatically sets the ends_at column in your database. This column will be used to know when the subscribed method should begin returning false. For instance, if a customer cancels his subscription on March 1st, but the subscription wasn't scheduled to end until March 5th, the subscribed method continues to return true until March 5th.

You can determine if a user has cancelled his/her subscription but is still on his/her "grace period" using the onGracePeriod method:

if ($user->subscription('main')->onGracePeriod()) {
    //
}

If you want to cancel a subscription immediately, you should call the cancelNow method on the user's subscription:

$user->subscription('main')->cancelNow();

Resuming Subscriptions

If a user has cancelled his/her subscription and you want to resume it, you can use the resume method. The user must still be on his/her grace period in order to resume a subscription:

$user->subscription('main')->resume();

In the case where the user cancels a subscription and then resumes that subscription before the subscription has fully expired, they won't be billed immediately. Instead, his/her subscription is re-activated, and the user will be billed on the original billing cycle.

Subscription Trials

With Credit Card Up Front

If you want to offer trial periods to your customers while still collecting payment method information up front, make use of the trialDays method when creating your subscriptions:

$user = User::find(1);

$user->newSubscription('main', 'monthly')
            ->trialDays(10)
            ->create($stripeToken);

This method sets the trial period ending date on the subscription record within the database, as well as instruct Stripe / Braintree to not begin billing the customer until after this ending date.

The trialUntil method will allow you to provide a DateTime instance to specify when the trial period should end:

use Carbon\Carbon;

$user->newSubscription('main', 'monthly')
            ->trialUntil(Carbon::now()->addDays(10))
            ->create($stripeToken);

You can determine if the user is within their trial period using either the onTrial method of the user instance, or by using the onTrial method of the subscription instance. The two examples below are similar:

if ($user->onTrial('main')) {
    //
}

if ($user->subscription('main')->onTrial()) {
    //
}

Without Credit Card Up Front

If you want to offer trial periods without collecting the user's payment method information up front, you can set the trial_ends_at column on the user record to your desired trial ending date. The normal convention is to do this during user registration:

$user = User::create([
    // Populate other user properties...
    'trial_ends_at' => now()->addDays(10),
]);

Cashier will refer to this type of trial as a "generic trial", since it is not attached to any existing subscription. The onTrial method on the User instance returns true if the current date is not past the value of trial_ends_at:

if ($user->onTrial()) {
    // User is within their trial period...
}

You can also use the onGenericTrial method if you want to know specifically that the user is within their "generic" trial period and hasn't created an actual subscription yet:

if ($user->onGenericTrial()) {
    // User is within their "generic" trial period...
}

Immediately you are ready to create an actual subscription for the user, you can use the newSubscription method as usual:

$user = User::find(1);
$user->newSubscription('main', 'monthly')->create($stripeToken);

Customers

Creating Customers

Occasionally, you may want to create a Stripe customer without beginning a subscription. You can accomplish this using the createAsStripeCustomer method:

$user->createAsStripeCustomer();

Once the customer is created in Stripe, you can begin a subscription at a later date.

The equivalent of this method in Braintree is the createAsBraintreeCustomer method.

Cards

Retrieving Credit Cards

The cards method on the billable model instance will return a collection of Laravel\Cashier\Card instances:

$cards = $user->cards();

If you want to retrieve the default card, the defaultCard method can be used;

$card = $user->defaultCard();

Determining If A Card Is On File

You can check if a customer has a credit card attached to their account using the hasCardOnFile method:

if ($user->hasCardOnFile()) {
    //
}

Updating Credit Cards

The updateCard method can be used to update a customer's credit card information. This method will accept a Stripe token and will assign the new credit card as the default billing source:

$user->updateCard($stripeToken);

If you want to sync your card information with the customer's default card information in Stripe, you can use the updateCardFromStripe method:

$user->updateCardFromStripe();

Deleting Credit Cards

If you want to delete a card, you have to first retrieve the customer's cards with the cards method. Then, you can call the delete method on the card instance you wish to delete:

foreach ($user->cards() as $card) {
    $card->delete();
}

The deleteCards method deletes all of the card information stored by your application:

$user->deleteCards();

Handling Stripe Webhooks

Both Braintree and Stripe can notify your application of a variety of events via webhooks. For you to handle Stripe webhooks, you have to define a route that points to Cashier's webhook controller. This controller handles all incoming webhook requests and dispatches them to the proper controller method:

Route::post(
    'stripe/webhook',
    '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);

By default, this controller automatically handles cancelling subscriptions that have too many failed charges (as defined by your Stripe settings), customer updates, customer deletions, subscription updates, and credit card changes; however, as you will soon discover, you can extend this controller to handle any webhook event you want.

Webhooks & CSRF Protection

Since Stripe webhooks have to bypass Laravel's CSRF protection, ensure that you list the URI as an exception in your VerifyCsrfToken middleware or you list the route outside of the web middleware group:

protected $except = [
    'stripe/*',
];

Defining Webhook Event Handlers

Cashier automatically will handle subscription cancellation on failed charges, but if you have additional Stripe webhook events you want to handle, you should extend the Webhook controller. Your method names have to correspond to Cashier's expected convention, specifically, methods have to be prefixed with handle and the "camel case" name of the Stripe webhook you want to handle. For instance, if you want to handle the invoice.payment_succeeded webhook, you have to add a handleInvoicePaymentSucceeded method to the controller:

<?php

namespace App\Http\Controllers;

use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;

class WebhookController extends CashierController
{
    /**
     * Handle invoice payment succeeded.
     *
     * @param  array  $payload
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function handleInvoicePaymentSucceeded($payload)
    {
        // Handle The Event
    }
}

Next, you have to define a route to your Cashier controller within your routes/web.php file:

Route::post(
    'stripe/webhook',
    '\App\Http\Controllers\WebhookController@handleWebhook'
);
.

Failed Subscriptions

What will happen when a customer's credit card expires? No worries - Cashier includes a Webhook controller that easily cancels the customer's subscription for you. All you have to do is point a route to the controller:

Route::post(
    'stripe/webhook',
    '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);

That's it! Failed payments are captured and handled by the controller. The controller cancels the customer's subscription when Stripe determines the subscription has failed (normally after three failed payment attempts).

Verifying Webhook Signatures

To secure your webhooks, you can use Stripe's webhook signatures. For convenience sake, Cashier will automatically include a middleware which validates that the incoming Stripe webhook request is valid.

To enable this webhook verification, you have to ensure that the stripe.webhook.secret configuration value is set in your services configuration file. The webhook secret can be retrieved from your Stripe account dashboard.

Handling Braintree Webhooks

Both Stripe and Braintree notifies your application of a variety of events via webhooks. If you want to handle Braintree webhooks, you should define a route that points to Cashier's webhook controller. This controller handles all incoming webhook requests and dispatches them to the proper controller method:

Route::post(
    'braintree/webhook',
    '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);

By default, this controller automatically handles cancelation of subscriptions that have too many failed charges (as defined by your Braintree settings); however, as you will soon discover, you can extend this controller to handle just about any webhook event you like.

,strong>Webhooks & CSRF Protection

Since Braintree webhooks need to bypass the Laravel CSRF protection, ensure you list the URI as an exception in your VerifyCsrfToken middleware or you list the route outside of the web middleware group:

protected $except = [
    'braintree/*',
];

Defining Webhook Event Handlers

Cashier will automatically handle subscription cancellation on failed charges, but if you have additional Braintree webhook events you want to handle, then you should extend the Webhook controller. Your method names need to correspond to Cashier's expected convention, specifically, methods need to be prefixed with handle and the "camel case" name of the Braintree webhook that you wish to handle. For instance, if you want to handle the dispute_opened webhook, you should add a handleDisputeOpened method to the controller:

<?php

namespace App\Http\Controllers;

use Braintree\WebhookNotification;
use Laravel\Cashier\Http\Controllers\WebhookController as CashierController;

class WebhookController extends CashierController
{
    /**
     * Handle a new dispute.
     *
     * @param  \Braintree\WebhookNotification  $webhook
     * @return \Symfony\Component\HttpFoundation\Responses
     */
    public function handleDisputeOpened(WebhookNotification $webhook)
    {
        // Handle The Webhook...
    }
}

Failed Subscriptions

What happens in cases where a customer's credit card expires? No worries - Cashier includes a Webhook controller that could easily cancel the customer's subscription for you. You just need to point a route to the controller:

Route::post(
    'braintree/webhook',
    '\Laravel\Cashier\Http\Controllers\WebhookController@handleWebhook'
);

That's it! Failed payments are captured and handled by the controller. The controller cancels the customer's subscription when Braintree determines the subscription has failed (normally after three failed payment attempts). Don't forget: you will have to configure the webhook URI in your Braintree control panel settings.

Single Charges

Simple Charge

If you would love to make a "one off" charge against a subscribed customer's credit card, you can use the charge method on a billable model instance.

// Stripe will Accept Charges In Cents...
$stripeCharge = $user->charge(100);

// Braintree will Accept Charges In Dollars...
$user->charge(1);

The charge method will accept an array as its second argument, this allows you to pass any options you wish to the underlying Stripe / Braintree charge creation. You should consult the Stripe or Braintree documentation regarding the options available to you when creating charges:

$user->charge(100, [
    'custom_option' => $value,
]);

The charge method throws an exception if the charge fails. In the case where the charge is successful, the full Stripe / Braintree response is returned from the method:

try {
    $response = $user->charge(100);
} catch (Exception $e) {
    //
}

Charge With Invoice

Sometimes you may want to make a one-time charge and also generate an invoice for the charge so that you can offer a PDF receipt to your customer. The invoiceFor method will let you do just that. For instance, let us invoice the customer $5.00 for a "One Time Fee":

// Stripe will Accept Charges In Cents...
$user->invoiceFor('One Time Fee', 500);

// Braintree will Accept Charges In Dollars...
$user->invoiceFor('One Time Fee', 5);

The invoice is charged immediately against the user's credit card. The invoiceFor method will also accept an array as its third argument. This array will contain the billing options for the invoice item. The fourth argument that is accepted by the method is also an array. This final argument will accept the billing options for the invoice itself:

$user->invoiceFor('Stickers', 500, [
    'quantity' => 50,
], [
    'tax_percent' => 21,
]);

In the case where you are using Braintree as your billing provider, you need to include a description option when calling the invoiceFor method:

$user->invoiceFor('One Time Fee', 500, [
    'description' => 'your invoice description here',
]);

Refunding Charges

If you want to refund a Stripe charge, you can use the refund method. This method will accept the Stripe charge ID as its only argument:

$stripeCharge = $user->charge(100);
$user->refund($stripeCharge->id);

Invoices

You can easily retrieve an array of a billable model's invoices using the invoices method:

$invoices = $user->invoices();
// Includes pending invoices in the results...
$invoices = $user->invoicesIncludingPending();

When you are listing the invoices for the customer, you can use the invoice's helper methods to display the relevant invoice information. For instance, you may want to list every invoice in a table, this allows the user to easily download any of them:

<table>
    @foreach ($invoices as $invoice)
        <tr>
            <td>{{ $invoice->date()->toFormattedDateString() }}</td>
            <td>{{ $invoice->total() }}</td>
            <td><a href="/user/invoice/{{ $invoice->id }}">Download</a></td>
        </tr>
    @endforeach
</table>

Generating Invoice PDFs

From within a controller or route, you should use the downloadInvoice method to generate a PDF download of the invoice. This method automatically generates the proper HTTP response to send the download to the browser:

use Illuminate\Http\Request;
Route::get('user/invoice/{invoice}', function (Request $request, $invoiceId) {
    return $request->user()->downloadInvoice($invoiceId, [
        'vendor'  => 'Your Company',
        'product' => 'Your Product',
    ]);
});

Previous: Laravel (5.7) Laravel Horizon
Next: Laravel (5.7) Laravel Envoy



Follow us on Facebook and Twitter for latest update.