After installing Spark, your application's .env
file will contain environment variables that need to be populated. If you are using Stripe, you will need to populate the STRIPE_KEY
and STRIPE_SECRET
environment variables. You may retrieve these values from your Stripe control panel.
If you are using Braintree, you will need to populate the BRAINTREE_ENV
, BRAINTREE_MODEL
, BRAINTREE_MERCHANT_ID
, BRAINTREE_PUBLIC_KEY
, and BRAINTREE_PRIVATE_KEY
variables.
In order to display a list of your customer's invoices, you must configure the appropriate webhooks on Stripe or Braintree. Stripe webhooks should be configured to point to the /webhook/stripe
URI. The Braintree subscription_canceled
, subscription_charged_successfully
, and subscription_expired
webhook events should be configured to point to the /webhook/braintree
URI.
All of your application's billing plans are defined in the app/Providers/SparkServiceProvider
class. Of course, this service provider is automatically generated when you create a new Spark project. Within the service provider's booted
method, you will find a sample plan already defined. The default Spark application defines a "no card up front" trial plan, which will allow new users to sign up for your application without providing their billing details during their initial registration.
You may modify this plan and define additional plans as needed. The first argument given to the plan
method is the displayable name of the plan while the second argument is the plan's ID on either Stripe or Braintree:
Spark::plan('Pro', 'monthly-pro')
->price(20)
->features([
'Feature 1',
'Feature 2',
'Feature 3',
]);
The features
method accepts an array of features provided by a given plan. These features will be displayed when a user clicks the "Plan Features" modal on the registration or settings views.
If you would like to define a plan that has a yearly billing frequency, you may use the yearly
method:
Spark::plan('Pro', 'yearly-pro')
->price(100)
->yearly()
->features([
'Feature 1',
'Feature 2',
'Feature 3',
]);
Team billing plans allows teams to subscribe to billing plans instead of users. For example, a user may create one team for their business that is subscribed to the "Pro" plan, while having another team for their personal projects that is subscribed to the "Basic" plan.
If your application offers team plans instead of individual user plans, you should generate your application using the --team-billing
switch. This will generate a SparkServiceProvider
stub that defines team plans instead of individual plans:
spark new project-name --team-billing
When your application provides team billing, the booted
method of your SparkServiceProvider
should typically contain a configuration like the following:
Spark::useStripe()->noCardUpFront()->teamTrialDays(10);
Spark::freeTeamPlan()
->features([
'First', 'Second', 'Third'
]);
Spark::teamPlan('Basic', 'team-basic')
->price(10)
->features([
'First', 'Second', 'Third'
]);
Note: You may define user and team billing plans within the same application.
Since it's common to limit the number of team members a given team may have for a given plan, Spark makes configuring these constraints a breeze. You may use the maxTeamMembers
method when defining your team plan. Once this is configured, Spark will automatically validate that attempted plan changes are valid based on the number of team members allowed for the plan:
Spark::teamPlan('Basic', 'team-basic')
->price(10)
->maxTeamMembers(5)
->features([
'Five Team Members',
'Feature 2',
'Feature 3',
]);
By default, Spark is configured to allow users to register without providing their credit card. After registering, they may choose to fully subscribe to the application at any time before their trial period ends. The number of trial days granted to a user when they register is defined by the trialDays
method that is called on the Spark
instance in the SparkServiceProvider
. Or, if you are using team billing, you should use the teamTrialDays
method instead:
// When using user billing...
Spark::useStripe()->noCardUpFront()->trialDays(10);
// When using team billing...
Spark::useStripe()->noCardUpFront()->teamTrialDays(10);
If a customer registers while Spark is configured in this way, they will be placed on a "generic trial", meaning they will not actually be subscribed to any of your plans. If you would like to know if a customer is on a "no card up front", generic trial, you may use the onGenericTrial
method on the user (or team if using team billing) instance. This method may be used to limit access to certain features when a user is on a generic trial:
// Via user...
$user->onGenericTrial();
// Via team...
$team->onGenericTrial();
By default, Spark is configured to allow users to register without providing their credit card. After registering, they may choose to fully subscribe to the application at any time before their trial period ends.
However, you may wish to choose to gather credit card details up front. To do so, remove the calls to noCardUpFront
and trialDays
/ teamTrialDays
from your SparkServiceProvider
. This will instruct the registration view to gather the user's credit card details up front.
If you would like to gather credit card details up front but also offer trial periods on your plans, you will need to define the trialDays
on the plans themselves:
Spark::plan('Basic', 'team-basic')
->price(10)
->trialDays(10)
->features([
'Five Team Members',
'Feature 2',
'Feature 3',
]);
Note: If you define your plans in this manner, users will be automatically billed once their trial period expires unless they cancel their subscription.
Note: This feature is only supported if your application is using Stripe for subscription billing.
If you would like to collect your customer's billing addresses during registration and during payment method updates, you may call the collectBillingAddress
method on the Spark instance in the booted
method of your SparkServiceProvider
:
Spark::collectBillingAddress();
Sometimes you may wish to limit plans by a given resource. For example, perhaps your project management application's "Pro" plan only allows a user to create 20 projects, while your "Enterprise" plan allows users to create up to 50 projects. In this example, a user on the Enterprise plan could not downgrade to the Pro plan if they still have 40 projects attached to their account.
Spark allows you to customize these rules using the checkPlanEligibilityUsing
method which accepts a Closure as its single argument. This Closure receives the current user and the plan the user is attempting to subscribe to. It is up to you to either return true
or false
from this callback based on whether the user should be allowed to change to the given plan. You can call this method from the booted
method of your application's SparkServiceProvider
:
Spark::checkPlanEligibilityUsing(function ($user, $plan) {
if ($plan->name == 'pro' && count($user->todos) > 20) {
return false;
}
return true;
});
Alternatively, you may pass a class / method reference:
Spark::checkPlanEligibilityUsing('EligibilityChecker@handle');
If you would like to provide a specific reason the user is not allowed to switch to a given plan, you may throw a IneligibleForPlan
exception from the callback. The given reason will be displayed to the user on the subscription update screen:
use Laravel\Spark\Exceptions\IneligibleForPlan;
Spark::checkPlanEligibilityUsing(function ($user, $plan) {
if ($plan->name == 'pro' && $user->todos->count() > 20) {
throw IneligibleForPlan::because('You have too many to-dos.');
}
return true;
});
If you no longer wish to offer a given plan to your customers, you should not delete it entirely from your service provider. Instead use the archived
method when defining the plan. This will prevent new customers from choosing the plan but will allow existing customers to continue using the plan:
Spark::plan('Pro', 'yearly-pro')
->archived()
->price(100)
->yearly()
->features([
'Feature 1',
'Feature 2',
'Feature 3',
]);
Spark's per-seat pricing makes it possible to charge your customers for each team, for each team member, or by any other custom metric you wish to charge by. For example, if you are building project management software, you may wish to charge your users per project.
To get started, your application should typically have two subscription plans: a free plan and a plan that defines the price "per seat".
If your application uses teams, you may charge your users based on how many teams they manage. When a team is created or removed from a team, Spark will automatically update the subscription's "quantity" on Stripe or Braintree. To configure this style of billing, the booted
method of your SparkServiceProvider
should look like the following:
Spark::useStripe()->noCardUpFront()->trialDays(10);
Spark::chargePerTeam();
Spark::plan('Basic', 'monthly-basic')
->price(10)
->features([
'First', 'Second', 'Third'
]);
If your application uses teams, you may charge your users based on how many users they add to a team. When a team member is added or removed from a team, Spark will automatically update the subscription's "quantity" on Stripe or Braintree. To configure this style of billing, the booted
method of your SparkServiceProvider
should look like the following:
Spark::useStripe()->noCardUpFront()->teamTrialDays(10);
Spark::chargeTeamsPerMember();
Spark::teamPlan('Basic', 'team-basic')
->price(10)
->features([
'First', 'Second', 'Third'
]);
Next, let's take a look at at charging per a custom target. In this example, let's assume you are building project management software and you would like to charge your customers per project. First, you should call the chargePerSeat
method within the booted
method of the SparkServiceProvider
:
Spark::chargePerSeat('Projects', function ($user) {
return $user->projects()->count();
});
The chargePerSeat
method accepts two arguments: the name of the item you are charging for and a Closure that calculates the total number of items attached to the user. If you would like to charge teams per seat, you may use the chargeTeamsPerSeat
method:
Spark::chargeTeamsPerSeat('Projects', function ($team) {
return $team->projects()->count();
});
Next, you will need to call the addSeat
/ removeSeat
methods from the relevant places in your application. So, for this example, you would call these methods when your users create or delete projects. The addSeat
method will "increment" your subscription plan's quantity and the removeSeat
method will "decrement" the subscription plan's quantity:
$user->addSeat();
$user->removeSeat();
Alternatively, you may directly provide the number of "seats" the user's subscription currently has:
$user->updateSeats(5);
Of course, if your application is team based, you should call these methods on a team instance:
$team->addSeat();
$team->removeSeat();
$team->updateSeats(5);
During installation, Spark automatically registers a subscribed
middleware in your application's app\Http\Kernel.php
file. This middleware may be used to restrict access to your application's routes based on the user's subscription status.
To prevent a user from accessing a given route when they are not subscribed to your application, assign the subscribed
middleware to the route. If the user is not subscribed, they will be redirected to the subscription settings page:
Route::get('/paid', function () {
//
})->middleware('subscribed');
If your application is using team billing, you may check if the current team is subscribed using the teamSubscribed
middleware:
Route::get('/paid', function () {
//
})->middleware('teamSubscribed');
If you would like to define a coupon that should be applied to every new registration for your application, you may use the Spark promotion
method. This allows you to run promotional discounts for holidays or other special occasions. However, the promotional discount may only be applied during new registrations. You may call the promotion
method from the booted
method of your SparkServiceProvider
:
Spark::promotion('coupon-code');
Laravel Cashier, which Spark uses to provide subscription billing, supports customizing your application's billing currency. You may customize the currency by calling the useCurrency
method on the Cashier instance. The useCurrency
method accepts the currency abbreviation as its first argument and the currency symbol as its second:
Laravel\Cashier\Cashier::useCurrency('eur', '€');
You may call this method from the booted
method of your App\Providers\SparkServiceProvider
class.
Note: The specified currency must be supported by Stripe or Braintree.
By default, Spark is configured to charge a prorated amount to the customer if a subscription was changed in the middle of a billing cycle. However, you can disable proration so that no changes are made to the subscription until the beginning of the next billing cycle. To disable proration, call the noProrate
method in the booted
method of your SparkServiceProvider
:
Spark::noProrate();
Spark fires several billing related Laravel events. Several of these events already have listeners assigned to them by default. You can view a list of these listeners in your App\Providers\EventServiceProvider
class.
All events listed in the following table are relative to the Laravel\Spark\Events
namespace:
Event | Description |
---|---|
PaymentMethod\BillingAddressUpdated |
The customer's billing address has been updated. |
PaymentMethod\VatIdUpdated |
The customer's European VAT ID has been updated. |
Subscription\SubscriptionCancelled |
A user's subscription was cancelled. |
Subscription\SubscriptionUpdated |
A user's subscription was updated. |
Subscription\UserSubscribed |
A user started a new subscription to the application. |
In addition to these user related subscription events, they are also events for team subscriptions. All events listed in the following table are relative to the Laravel\Spark\Events
namespace:
Event | Description |
---|---|
Teams\Subscription\SubscriptionCancelled |
A team's subscription has been cancelled. |
Teams\Subscription\SubscriptionUpdated |
A team's subscription has been updated. |
Teams\Subscription\TeamSubscribed |
A team has begun a new subscription. |