In Part 1, we designed the data model and backend logic for add-on subscriptions in Laravel Spark. Now it's time to deliver the goods — building the frontend UI and provisioning add-ons when a user clicks "Install".
Delivering the Goods — Provisioning an Add-on
When the user clicks "Install", I want to open a modal where they can:
- Confirm the plan they have selected
- Specify which team it should belong to (if they have more than one)
- Enter their credit card details if they haven't previously provided them
Building the Install Modal
Spark uses Vue.js for its frontend components. We'll extend this pattern for our add-on modal. Create a new component at resources/assets/js/components/addon-install.js:
Vue.component('addon-install', {
props: ['addon'],
data() {
return {
form: new SparkForm({
team_id: null,
stripe_token: null,
}),
installStep: 'confirm',
}
},
methods: {
install() {
Spark.post('/api/addons/' + this.addon.id + '/install', this.form)
.then(() => {
this.$emit('installed');
$('#modal-addon-install').modal('hide');
});
}
}
});
The Install Endpoint
Now let's build the API endpoint that handles the installation request. In your AddonController:
public function install(Request $request, Addon $addon)
{
$team = Team::findOrFail($request->team_id);
$this->authorize('install', $addon);
$subscription = $team->newSubscription($addon->slug, $addon->stripe_plan)
->create($request->stripe_token);
event(new AddonInstalled($team, $addon, $subscription));
return response()->json(['status' => 'installed']);
}
Fire an event when an add-on is installed. This keeps your controller thin and lets you attach listeners — sending welcome emails, provisioning resources, logging installs — without cluttering your controller logic.
Handling the AddonInstalled Event
Create a listener that provisions whatever the add-on provides. For example, if the add-on unlocks extra storage:
class ProvisionAddonStorage
{
public function handle(AddonInstalled $event)
{
$event->team->update([
'storage_limit' => $event->team->storage_limit + $event->addon->storage_gb,
]);
}
}
The Vue Modal Template
Register the modal in your main layout. Spark uses Bootstrap modals, so we'll follow that convention:
<addon-install
:addon="selectedAddon"
@installed="onAddonInstalled">
</addon-install>
Handling Webhooks for Add-ons
Stripe sends webhook events when subscriptions renew, fail, or are cancelled. Extend Spark's webhook controller to handle your add-on subscriptions:
protected function handleCustomerSubscriptionDeleted(array $payload)
{
$subscription = Subscription::where('stripe_id', $payload['data']['object']['id'])->first();
if ($subscription && $subscription->isAddon()) {
event(new AddonCancelled($subscription->team, $subscription->addon));
}
parent::handleCustomerSubscriptionDeleted($payload);
}
With these pieces in place, you have a fully functional add-on subscription system built on top of Laravel Spark. Users can install and uninstall add-ons, teams are billed correctly, and your event system keeps everything in sync.