When Flare was launched, we used Laravel Spark classic to take care of all user, team and billing functionality. We recently migrated to the latest version of Laravel Spark. It offers a beautiful billing management screen. Here’s how it looks like.
Our new Spark-powered billing portal allows you to see all available plans easily, subscribe, download invoices, etc…
By migrating to the latest version of Spark, we could vastly simplify our codebase. In this blog post, we’d like to share how we performed this migration and how we customised Laravel Spark.
How we used Laravel Spark classic #
When we started work on Flare, we decided very early on that we didn’t want to code everything around team management and billing ourselves. Using Laravel Spark was a given since we already had some experience with it by building Oh Dear.
At Spatie, we very much prefer working with React. We’re also very detail focused when creating a UI. Because Spark classic was built with Vue components, that didn’t work how we wanted, we made a drastic decision: we started using Spark in a headless way. We use everything that Spark classic offered on the backend but used nothing of the front end components. We created our own React components that hooked in the Spark classic back end.
For us, this worked out pretty well. Even though we had some work creating our own React billing components, Spark classic still saved us quite some time.
Moving to the newest version of Spark #
Earlier this year, the Laravel team introduced a new version of Spark. The significant difference between the classic and the new Spark is that the new Spark is a separate billing portal instead of a Laravel starter template project on steroids. The user and team management features have been removed; they now reside in a separate free package called JetStream.
We started migrating to the newest version of Spark for two reasons:
Taking care of user and team management #
We relied on the user and team management of the Spark classic. The new Spark doesn’t offer this functionality. We could try to migrate to JetStream, but we opted for a much simpler solution.
In the old Spark, you had to let
Team model of your app extend the base
Team classes of Spark.
Before removing the old Spark dependency, we copied over all the code from
LaravelSparkTeam to our own
AppDomainTeamModelsTeam model (and did the same with the
Apart from a few other minor changes, this refactor did work. Our custom users/team UI just kept functioning normally.
We did the same refactor with the other code that extended stuff from
LaravelSpark: team invitations, API token generation, auth controllers.
Because we used old Spark in a headless way and did not rely on any of its UI, just pulling the back end to our own codebase was easy.
After everything was copied, we did a round of refactoring where we simplified the imported Spark code. Any Spark features that Spark offered that we didn’t use were removed.
Migrating the database #
By comparing the tables and stored values of the production version of Flare with the local version of Flare with new Spark installed in it, we could write migrations that modify the table structure towards what the latest version of spark expects. We’ve also written migrations to copy the old data before any tables or columns are dropped.
You’ll find the migrations we’ve used in this public gist on GitHub. We tested this migration by copying over parts of production data to our local environment to verify if the migrations work as expected.
Customising VAT handling #
For the most part, we’re using the Spark billing portal as is. The only significant customisation we’ve done revolves around VAT handling.
To verify European VAT ids, Spark uses the European VIES API. We’ve noticed that this API is down a lot, resulting in people that cannot subscribe. Of course, this is not good for business. It would be better to just accept the VAT number when the API is down and verify it later when the API is back up.
If you follow the rules strictly, you’ll argue that, in theory, this isn’t allowed as you are obliged to verify a VAT number before you invoice it. But in practice, we see that all people that sign up are using valid VAT numbers.
Luckily, it’s easy to override how Spark handles VAT so we can implement this new behaviour. Spark handles VAT calculation in an action class which can be overridden in de container. In our
SparkServiceProvider, we’ve added this binding.
Inside of the
CalculateVatRate we can our own custom
AppDomainSubscriptionSupportVatCalculationVatCalculator class. Here’s the code for that action.
Let’s now take a look at the
AppDomainSubscriptionSupportVatCalculationVATCalculator itself. We’re not going over all of this code in detail. The extra features that this code has over that ships with Spark are:
And finally, here is the command the will validate VAT numbers that have not been validated yet. We’ll also validate VAT numbers again every 30 days after they validate. This way, when a VAT number gets retracted, we’ll also know it.
If a VAT number wasn’t valid, we’ll send a notification to our Slack channel.
It sure is quite a lot of code to get VAT handling right. We considered creating our own VAT calculator package. We held off that plan as Stripe recently announced a new product, called Stripe Tax, that can calculate VAT. We hope that Spark will offer support for Stripe Tax as soon as Stripe makes its new production publicly available.
By removing our custom UI for billing, we could make our codebase about 12,5K lines lighter.
✨ https://t.co/IEFWJgl4Fq now uses the latest version of Spark ✨
This allowed us to remove 12,5K lines of our code base pic.twitter.com/VJOkvycLAJ
— Freek Van der Herten (@freekmurze)
Thanks to Spark’s latest version, we now have a beautiful billing portal that we don’t have to maintain ourselves. We’re very grateful for the effort that the Laravel team put into this product.
In this blog post, we only touched upon a few aspects of the migration. If you want to read more details about upgrading Spark and JetStream, head over to this blog post on how our sibling service Oh Dear was upgraded.
This content was originally published here.