mmanos/laravel-billing

A billing package for Laravel 4.

v1.1.9 2017-11-11 20:55 UTC

This package is not auto-updated.

Last update: 2024-11-23 17:20:36 UTC


README

This package provides an expressive, fluent interface to a billing services gateway. It handles almost all of the boilerplate code for managing billing customers, subscriptions, and individual charges. The usage and feature set was heavily influenced by the popular Laravel Cashier package. However, it has these major differences:

  • Support for more than one billing gateway (via Facades)
  • Support for multiple subscriptions per customer
  • Support for multiple credit cards per customer
  • Support for individual charges
  • Support for free subscription plans

It currently comes bundled with drivers for these billing services:

Note: Not all features are supported by every billing gateway. See the Gateway Limitations section below for more information.

Installation

Composer

Add this to your composer.json file, in the require object:

"mmanos/laravel-billing": "dev-master"

After that, run composer install to install the package.

Service Provider

Register the Mmanos\Billing\BillingServiceProvider in your app configuration file.

Dependencies

The following composer dependencies are needed for the listed billing gateways:

  • Stripe: stripe/stripe-php
  • Braintree: braintree/braintree_php

Configuration

Config File

Publish the default config file to your application so you can make modifications.

$ php artisan config:publish mmanos/laravel-billing

Customer Migration

Before using this package, we'll need to add several columns to the table that will represent your billing customer. You can use the laravel-billing:customer-table Artisan command to create a migration to add the necessary columns.

For example, to add the columns to the users table use:

$ php artisan laravel-billing:customer-table users

Subscription Migrations

Next, we'll need to add several columns to the table that will represent your billing subscription plans. You can use the laravel-billing:subscription-table Artisan command to create a migration to add the necessary columns.

For example, if you have a hosting company and want to allow your customers add a subscription for a website, you might use:

$ php artisan laravel-billing:subscription-table websites

Note: If you only need one subscription per customer, you may use the same table for both the customer migration and the subscription migration (eg. users table).

Note: You may also support multiple subscriptions per customer by adding these fields to more than one table.

Run Migrations

Once the migration has been created, simply run the migrate command.

Customer Model Setup

Next, add the CustomerBillableTrait to your customer model definition:

use Mmanos\Billing\CustomerBillableTrait;

class User extends Eloquent
{
	use CustomerBillableTrait;

}

You should also define a subscriptionmodels method that returns all models that represent any billing subscriptions for a customer. This allows the customer model to propagate credit card changes and status changes (if the customer is deleted) to all of it's subscription models.

public function subscriptionmodels()
{
	// Return an Eloquent relationship.
	return $this->hasMany('Website');
	
	// Or, return an array or collection of models.
	return Website::where('user_id', $this->id)->get();
	
	// Or, return an array of collections.
	return array(
		Website::where('user_id', $this->id)->get(),
		Domain::where('user_id', $this->id)->get(),
	);
}

Subscription Model Setup

Then, add the SubscriptionBillableTrait to your subscription model definition(s):

use Mmanos\Billing\SubscriptionBillableTrait;

class Website extends Eloquent
{
	use SubscriptionBillableTrait;

}

You should also define a customermodel method that returns the model representing the customer who owns this subscription. This ensures a subscription has access to the necessary customer information when interacting with the different gateway APIs.

public function customermodel()
{
	// Return an Eloquent relationship.
	return $this->belongsTo('User', 'user_id');
	
	// Or, Return an Eloquent model.
	return User::find($this->user_id);
}

Model Definitions

Finally, to add the ability to look up a model based on it's gateway ID (customer ID, or subscription ID), you need to define the array of Eloquent models acting as a Customer or Subscription.

Add your customer class to the customer_models property in this package's config file:

'customer_models' => array('User')

And add the subscription class(es) to the subscription_models property in this package's config file:

'subscription_models' => array('Website')

These values are primarily used by the WebhookControllers to help locate the corresponding Eloquent model from the gateway's ID when a new billing event is received.

Customers

Billing customers can be created and managed separately from subscriptions or charges.

Creating A Customer

Once you have a customer model instance, you can create the customer in the billing gateway using a gateway-specific credit card token:

$user = User::find(1);

$user->billing()->withCardToken('token')->create();

If you would like to apply a coupon when creating the customer, you may use the withCoupon method:

$user->billing()->withCardToken('token')->withCoupon('code')->create();

The billing method will automatically update your database with the billing gateway's customer ID and other relevant billing information.

If you would like to specify additional customer details, you may do so by passing them in to the create method:

$user->billing()->withCardToken('token')->create(array(
	'email' => $email,
));

Updating A Customer

To update an existing customers primary credit card information, or to add a coupon to their existing account, you may use the update method:

$user->billing()->withCardToken('token')->withCoupon('code')->update();

Deleting A Customer

Deleting a customer will delete their account from the billing gateway and delete all existing subscriptions:

$user->billing()->delete();

Multiple Credit Cards

You may add more than one credit card to a customer using the creditcards method:

$card = $user->creditcards()->create('credit_card_token');

Updating an existing credit card's information (such as expiration date or billing address) is also possible:

$card->update(array(
	'exp_month' => '01',
	'exp_year'  => '2017',
));

Or delete an existing credit card:

$card->delete();

Retrieving and working with customer credit cards is pretty straight forward:

// Get all customer credit cards.
$cards = $user->creditcards()->get();

// Get the first card for a customer.
$card = $user->creditcards()->first();

// Find a card by it's ID.
$card = $user->creditcards()->find('card_id');

echo $card->id;
echo "{$card->brand} xxxx-xxxx-xxxx-{$card->last4} Exp: {$card->exp_month}/{$card->exp_year}"

Invoices

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

$invoices = $user->invoices()->get();

Or get the most recent invoice:

$invoice = $user->invoices()->first();

Or find an invoice by it's ID:

$invoice = $user->invoices()->find('invoice_id');

To display relevant invoice information, use the invoice properties:

$invoice->id;
$invoice->date;
$invoice->amount;
$invoice->items();
//...

Use the render method to generate a pre-formatted HTML version of the invoice:

$invoice->render();

Checking Customer Status

To verify that a customer has been created in the billing gateway, use the readyForBilling method:

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

Subscriptions

Subscribing To A Plan

Once you have a subscription model instance, you can subscribe a customer to a given plan:

$user = User::find(1);
$website = Website::find(1);

$user->subscriptions('monthly')->create($website);

You may apply a coupon specifically to the subsciption using the withCoupon method:

$user->subscriptions('monthly')->withCoupon('code')->create($website);

You may also specify a new credit card token to use with this subscription:

$user->subscriptions('monthly')->withCardToken('token')->create($website);

Or specify an existing credit card ID:

$user->subscriptions('monthly')->withCard('card_id')->create($website);

Note: If no card or card_token is specified, the default (initial) card associated with the customer will be used.

There may be times when you haven't yet created the customer or you don't have the customer model available for use when you want to create a subscription. In these cases you may subscribe to a plan directly on the subscription model using the subscription method:

$website->subscription('monthly')->create();

This method also supports the optional withCoupon and withCardToken methods:

$website->subscription('monthly')->withCoupon('code')->withCardToken('token')->create();

Note: The customer will automatically be retrieved using the customermodel method you defined in your subscription model above.

No Card Up Front

If your application offers a free-trial with no credit-card up front, set the cardUpFront property on your model to false:

protected $cardUpFront = false;

On model creation, be sure to set the trial end date on the model:

$website->billing_trial_ends_at = Carbon::now()->addDays(14);

$website->save();

It should be noted that any subscription commands executed on an on-trial subscription model will not be synced with the billing gateway if the customer is not ready for billing (created in billing system with at least one credit card). This allows the developer to perform the same create, swap, etc commands on a model while you wait for the customer to be created in the billing gateway.

If you have one or more pending, on-trial, subscriptions when the user does add their credit card, you can use the withSubscriptions flag to activate all pending, non-free, subscriptions in the billing gateway:

$user->billing()->withCardToken('token')->withSubscriptions()->create();

Swapping Subscriptions

To swap a user to a new subscription, use the swap method:

$website->subscription('premium')->swap();

If the user is on trial, the trial will be maintained as normal. Also, if a "quantity" exists for the subscription, that quantity will also be maintained.

Subscription Quantity

Sometimes subscriptions are affected by "quantity". For example, your application might charge $10 per month per user on an account. To easily increment or decrement your subscription quantity, use the increment and decrement methods:

$website->subscription()->increment();

// Add five to the subscription's current quantity...
$website->subscription()->increment(5);

$website->subscription()->decrement();

// Subtract five from the subscription's current quantity...
$website->subscription()->decrement(5);

Cancelling A Subscription

To cancel a subscription, use the cancel method:

$website->subscription()->cancel();

When a subscription is canceled, this package will automatically set the billing_subscription_ends_at column on your database. This column is used to know when the subscribed method should begin returning false. For example, if a customer cancels a subscription on March 1st, but the subscription was not scheduled to end until March 5th, the subscribed method will continue to return true until March 5th.

Resuming A Subscription

If a user has canceled their subscription and you wish to resume it, use the resume method:

$website->subscription('monthly')->resume();

You may also specify a new credit card token:

$website->subscription('monthly')->withCardToken('token')->resume();

If the user cancels a subscription and then resumes that subscription before the subscription has fully expired, they may not be billed immediately, depending on the billing gateway being used.

Free Subscription Plans

Sometimes you want to offer a free subscription plan. You can do that easily with the isFree flag. Any subscription flagged as free will not be synced to the billing gateway. However, you may continue to use the same subscription commands, for ease of use, as long as you use the isFree flag.

To create a free subscription:

$user->subscriptions('free-plan')->isFree()->create($website);

You may also swap from a paid plan to a free plan. This will cancel the subscription in the billing gateway and update the local record to reflect the free state:

$website->subscription('free-plan')->isFree()->swap();

Note: If you do not use the isFree flag, then it is assumed the plan is not free and the subscription will be created in the billing gateway.

Note: This package will attempt to maintain the subscription id from the billing gateway and resume the existing subscription when swapped from paid to free and then back to paid again.

Working With Subscriptions

To verify that a model is subscribed to your application, use the subscribed command:

if ($website->subscribed()) {
	//
}

To determine if the model has an active subscription in the billing gateway, use the billingIsActive method. This method would not return true if the model is currently on trial with cardUpFront set to false or if they have canceled and are on their grace period.

if ($website->billingIsActive()) {
	//
}

You may also determine if the model is still within their trial period (if applicable) using the onTrial method:

if ($website->onTrial()) {
	//
}

To determine if the model was once an active subscriber, but has canceled their subscription, you may use the canceled method:

if ($website->canceled()) {
	//
}

You may also determine if a model has canceled their subscription, but are still on their "grace period" until the subscription fully expires. For example, if a model subscription is canceled on March 5th that was scheduled to end on March 10th, the model is on their "grace period" until March 10th. Note that the subscribed method still returns true during this time.

if ($website->onGracePeriod()) {
	//
}

The everSubscribed method may be used to determine if the model has ever subscribed to a plan in your application:

if ($website->everSubscribed()) {
	//
}

To retrieve the customer model associated with a subscription use the customer method:

$customer = $website->customer();

You may also retrieve an array of subscriptions associated with a customer:

$subscriptions = $user->subscriptions()->get();

Charges

Creating individual charges on a customer, outside of subscriptions, is also possible.

Creating A Charge

Creating a new charge on a customer is easy:

$charge = $user->charges()->create(499);

Note: The amount of a charge is in cents.

To charge on a new credit card token, use the withCardToken method:

$charge = $user->charges()->withCardToken('token')->create(499);

You may also specify an existing credit card to use for a charge:

$charge = $user->charges()->withCard('card_id')->create(499);

Note: If no card or card_token is specified, the default (initial) card associated with the customer will be used.

Capturing A Charge

Sometimes you may want to preauthorize a charge before you capture it:

$charge = $user->charges()->create(499, array('capture' => false));

$charge->capture();

You may optionally specify the amount to capture as long as it is less than or equal to the amount preauthorized:

$charge = $user->charges()->create(499, array('capture' => false));

$charge->capture(array('amount' => 399));

Refunding A Charge

Refunding a charge is also possible:

$charge->refund();

Or optionally specify an amount to refund:

$charge->refund(array('amount' => 399, 'reason' => '...'));

Working With Charges

You may retrieve an array of all charges for a customer:

$charges = $user->charges()->get();

Or find the most recent charge for a customer:

$charge = $user->charges()->first();

Finding a charge from it's ID is also easy:

$charge = $user->charges()->find('charge_id');

Charge objects has several properties you might find useful, including: id, created_at, amount, paid, refunded, card, invoice_id, and description.

You may also access the associated invoice object (if available) from a charge:

$invoice = $charge->invoice();

Webhooks

This package comes bundled with a Webhook controller for each supported gateway, which can handle things such as failed or successful invoice payments, deleted subscriptions, and trial-will-end events.

Handling Webhook Events

To enable these events, just point a route to the appropriate gateway controller:

// Stripe.
Route::post('stripe/webhook', 'Mmanos\Billing\Gateways\Stripe\WebhookController@handleWebhook');

// Braintree.
Route::post('braintree/webhook', 'Mmanos\Billing\Gateways\Braintree\WebhookController@handleWebhook');

By default, this package does not try to delete a subscription after a certain number of failed payment attempts. Most billing gateways can do this automatically which would trigger a deleted subscription webhook event. When that happens, we will update our local model to record that change in status.

Handling Other Events

If you have additional webhook events you would like to handle, simply extend the Webhook controller and point the route to your controller.

class WebhookController extends Mmanos\Billing\Gateways\Stripe\WebhookController
{
	public function handleChargeDisputeCreated($payload)
	{
		// Handle The Event
	}
}

Model Events

To make it even easier to work with billing-related events, this package will trigger several convenient events on Eloquent models that you can hook into, so you don't have to do everything in the Webhook controller.

For example, to be notified when a model's trail will end, subscribe to the trialWillEnd model method:

Website::trialWillEnd(function ($website, $args = array()) {
	Log::info('Trial will end in ' . array_get($args, 'days') . ' day(s).');
});

Customer Events

There are several customer-related billing events you may subscribe to: customerCreated, customerDeleted, creditcardAdded, creditcardRemoved, creditcardUpdated, creditcardChanged, discountAdded, discountRemoved, discountUpdated, discountChanged, invoiceCreated, invoicePaymentSucceeded, and invoicePaymentFailed.

Subscription Events

There are several subscription-related billing events you may subscribe to: billingActivated, billingCanceled, planSwapped, planChanged, subscriptionIncremented, subscriptionDecremented, billingResumed, trialExtended, trialWillEnd, subscriptionDiscountAdded, subscriptionDiscountRemoved, subscriptionDiscountUpdated, and subscriptionDiscountChanged.

Gateway Limitations

Each billing gateway provides a different API and set of functionality. That being said, not all features are supported by every billing gateway. Here is a high level breakdown of the features NOT supported by each gateway:

Stripe

  • Does not support multiple subscriptions on separate credit cards (though this feature is coming). The withCard method will be ignored when creating a new subscription and the primary customer card will be used. However, multiple credit cards are supported on charges.

Braintree

  • Does not support any action regarding a credit card token. So creating/updating a user with a card token is not supported. Also, adding new credit cards is not supported. You must use their transparent redirect flows to accomplish this. You would also need to manually update the model fields when finished.
  • Does not support subscription quantities.
  • Does not support customer-specific discounts.
  • Does not support charge descriptions.
  • Does not return starting/ending customer balance on invoices.
  • Does not support resuming a canceled subscription. However, a new subscription will be created instead.
  • Does not support modifying the trial end date for an existing subscription. However, the existing subscription will be canceled and a new one created.

Local Driver

This package comes bundled with a Local gateway driver to simulate talking with a real billing gateway. All data is stored in a SQLite database located in app/storage/meta/billing-local.sqlite.

Adding Plans

To add a subscription plan to the local driver database, use the laravel-billing:local:create-plan Artisan command:

$ php artisan laravel-billing:local:create-plan

It will prompt you for the plan name, amount, interval, and trial period.

Adding Coupons

To add a coupon to the local driver database, use the laravel-billing:local:create-coupon Artisan command:

$ php artisan laravel-billing:local:create-coupon

It will prompt you for the coupon code, percent_off, amount_off, and duration.

Generating Credit Card Tokens

Even though this is not a real billing gateway, it is designed to simulate how a real gateway would respond. Therefore, you still need to generate a credit card token to attach to a customer or subscription.

To generate a valid token for this driver, simply JSON encode an object of fields you want to store with the credit card. For example:

var token = JSON.stringify({
	last4     : $('#card-number').val().substr(-4),
	exp_month : $('#exp-month').val(),
	exp_year  : $('#exp-year').val()
});