Shopify Billing Overview0:00
Let's talk about billing in Shopify and how we can charge merchants for using our apps. There are multiple ways to implement billing and it really depends on what you want to bill for. You can offer your app as a freemium app where they start as a free user and then you present them with a plan to pick at a later time. You can have subscription-based billing with additional user charges or just have a single one-time charge. Billing is for the most part handled by Shopify. You decide and implement the pricing structure and the billing model, and Shopify handles the actual billing, recurring charges, money collection, handles chargebacks, and so on. Basically, a charge is initiated by the merchant, which can happen during the app installation. It can be through plan upgrade or through individual purchase within the app. The app then creates a recurring or one-time charge for the merchant, and then
Custom Billable Middleware0:41
happen during the app installation. It can be through plan upgrade or through individual purchase within the app. The app then creates a recurring or one-time charge for the merchant, and then Shopify does the verification and response with a confirmation URL, redirecting the merchant to accept the charges or decline. Thankfully, Laravel Shopify makes it easier to handle this in our apps. Laravel Shopify provides the billable middleware that can be added to the routes, which basically automatically checks few things and handles the redirects. However, this middleware cannot be used if you're in SBA mode as of this recording. It might support SBA in future, so keep an eye on the documentation. For now, we're going to create our own billable middleware that is compatible with SBA. What we'll do is that instead of using this middleware, I'm going to copy this code right here, and I'm going to create my own billable middleware.
middleware that is compatible with SBA. What we'll do is that instead of using this middleware, I'm going to copy this code right here, and I'm going to create my own BillableMiddleware. Let's open the terminal. We'll run vendor/bin/sail artisan make:middleware BillableMiddleware. Let's open that middleware, and let's paste in the code here, and let's adjust it a little bit to make it work for SBA. First, we're going to check if the billing is enabled or not. So what we'll do is that we'll actually check if the billing is not enabled, and if it's not enabled, we'll just continue with the request this way. Let's get rid of that, move this back, and that's good. Otherwise, if we have the billing enabled, then we'll check if we are on SBA mode or not, and if it's a non-AJAX request. Because if we're on SBA mode and the request is not AJAX, then we can move on with the request as well. So we can do if Util::useNativeAppBridge,
and if it's a non-AJAX request. Because if we're on SBA mode and the request is not AJAX, then we can move on with the request as well. So we can do if util useNativeAppBridge, and the request is not AJAX, then we'll just move on with the request. So we'll copy this, paste it here. We need to negate this, and almost all of our requests are AJAX anyways, only the initial request is not. So for that request, we don't care to check for billing. Otherwise, then we can use this code to basically check if we need to do any redirects. So in here, we can keep these checks the way it is, but instead of returning the redirect route, we'll simply return a JSON response with the route being part of the response body. So we'll do response()->json(), and we'll have forceRedirectUrl equal to this route, and we'll also respond with 403 status code. To make this a bit more readable, I'm going to take this and assign it to
Axios Redirect Handling3:00
do response()->json(), and we'll have $forceRedirectUrl equal to this route(), and we'll also respond with 403 status code. To make this a bit more readable, I'm going to take this and assign it to a $redirectUrl variable, and let's put it in here, $redirectUrl equals this. Also, another thing we can add here is to make sure that we only do this if it's an AJAX request, because if for whatever reason this is not an AJAX request, we don't need to respond with json. So we can do and the $request is ajax(), only then do this. Otherwise, we'll just move on with the $request. Now let's add this middleware to all of our routes. So I'm going to copy this, let's open our web.php, and we'll add it right here. Now note that in our billable middleware, we are responding with 403 status code and including this $forceRedirectUrl property, because we have that handling in our useAxios custom hook in the front end. So if I open useAxios, we have this response interceptor right here,
Plans and Charges Tables3:58
and including this force redirect URL property, because we have that handling in our useAxios custom hook in the front end. So if I open useAxios, we have this response interceptor right here, where we're listening on that status, and if the status is 403, and we have the force redirect URL, then we redirect the user to that URL. There is actually one error here that I made previously. Instead of returning an error, we should be returning the promise this way. So we'll do a return Promise.reject(error). All right, so now that we have the billable middleware taken care of, let's inspect the billing-related tables that are provided by Laravel Shopify. So we have these two tables, plans and charges, and they are pretty self-explanatory. But basically, we need to insert some records in the plans table to enable billing. Now, as I mentioned before, the actual billing model will depend on your app and what you want to charge for it. You can have recurring or one-time
some records in the plans table to enable billing. Now, as I mentioned before, the actual billing model will depend on your app and what you want to charge for it. You can have recurring or one-time charge. You could set the interval to 30 days or annual. You can have capped amount. The capped amount and terms are used for usage-based charges, and we'll talk about that in just a couple of minutes. Trial days allows you to set the number of trial days on your plan. Test basically indicates that this plan is for testing purposes, and that way you don't need to have the credit card information or anything like that. Your development store is able to accept the charge regardless. And then on install basically determines if the plan should be presented to the merchant whenever they install the app. So as you can see, you have some flexibility here, and it is up to you how you want to set your billing up. Note that you still have to implement
whenever they install the app. So as you can see, you have some flexibility here, and it is up to you how you want to set your billing up. Note that you still have to implement the plan upgrade, downgrade, and all of that within your app's front end. We're going to set up the monthly recurring plan for this app. All right, so let's create some records in our plans table. I've already prepared a PlanSetter class for this task behind the scenes. However, you're free to set it up in the way that you're most comfortable with, whether that's using a custom command or a database migration. As you can see here, our app has two subscription plans, the free plan and the premium plan. You could technically set it up so that the free plan is actually not needed, which can be done by enabling the freemium configuration option and having a new plan ID on the user's record to indicate that they're on the free plan. That being said though,
Billing Types and Usage6:09
actually not needed, which can be done by enabling the freemium configuration option and having a new plan ID on the user's record to indicate that they're on the free plan. That being said though, having the free subscription plan has its advantages. For example, it simplifies the subscription management. Without this free plan to cancel a user subscription, you'd need to make an API call to delete the subscription manually. With the free subscription plan, the user can simply be downgraded from the premium to free. There is a catch here though with having a recurring plan with price zero. Shopify will reject the API call if you try to create a subscription with price zero, unless you're trying to create a subscription for usage charges. If we go back to the Shopify documentation here, we can see that there are three types of charges. There are three API resources here. We have application charge, recurring application charge,
go back to the Shopify documentation here, we can see that there are three types of charges. There are three API resources here. We have ApplicationCharge, RecurringApplicationCharge, and the UsageCharge. ApplicationCharge is a one-time only charge. RecurringApplicationCharge is a recurring charge on a 30-day billing cycle. And the UsageCharge is used to bill for additional amount during the month based on usage. This UsageCharge right here requires the RecurringApplicationCharge to be created, because as you can see here, it requires the recurringId. As you can see here, it states that to charge merchants just for usage, you need to create a plan with zero price, but set the cappedAmount and the terms. CappedAmount is the max amount the merchant can be charged for the UsageCharge, and the terms is just to display to the user explaining what exactly they will be charged for. So that is why in my free RecurringApplicationCharge plan here,
the merchant can be charged for the usage charges, and the terms is just to display to the user explaining what exactly they will be charged for. So that is why in my free recurring plan here, I have the capped amount set to a penny and the terms set to free plan, no charge. So you basically either have a recurring plan with price greater than zero or have a free plan with price zero, but have the capped amount greater than zero with terms. Since I don't plan to charge based on usage, I set the capped amount to a penny. Again, you could set this up in multiple ways, and it depends on how you want to do billing for your app. You can check Laravel Shopify's documentation for more information related to billing and how to create usage-based charges. Another thing to note here is that I have onInstall set to one on the first plan, which means that the merchant will be directed to the billing screen to accept the free recurring plan when they
Enable and Test Billing8:21
Another thing to note here is that I have on install set to one on the first plan, which means that the merchant will be directed to the billing screen to accept the free recurring plan when they install the app. In some cases, you may not want to do that, and you may want to have a freemium app where you don't present any billing screen to merchants on install. Only later on while they use the app, you might present them with a screen to upgrade to a premium plan or something like that. In that case, you would enable the freemium billing option in the configs and not have any plans set to one on install. All right, so let's actually test this out and run this sitter to populate our plans table. We'll open the terminal. We'll do vendor/bin/sail artisan db:seed --class=PlanSitter. That worked. Let's open the plans table, refresh, and we have two plans. One is free and the other one is premium. Next, we need to enable the billing through the configuration.
plan sitter. That worked. Let's open the plans table, refresh, and we have two plans. One is free and the other one is premium. Next, we need to enable the billing through the configuration. So let's open ShopifyApp configuration file. We'll search for billing. I think it's called billingEnabled. Yep. So we can use SHOPIFY_BILLING_ENABLED environment variable or enable it by default, but I'll use the .env file. So let's open .env, put it here, set it to true. Before we actually open this in browser and test it out in our Billable middleware, the redirect URL when generating the route expects host to be available on the request. However, when making the Ajax request, the host may not be available on the request. So we need to add this in to our Axios or Ajax calls. So if I open the useAxios custom hook, we can actually add that in in here. So we can do something like config.params equals config.params with the host parameter. Now we can get the host
So if I open the useAxios custom hook, we can actually add that in here. So we can do something like configParams equals configParams with the host parameter. Now we can get the host parameter from the window object because we store it there from within the App component. If we open the App component here, we see that we're getting the host from the location.search and then we're storing it within the window.shopifyHost. So we can take this and put it in here. Now for new installations, whenever a store installs our app, they will be taken to the billing page right away. However, we're testing this on an existing store that has installed our app already. So it's going to kick in only on the next Ajax call. So if I click on this button, we see that it's doing the redirects and then we're brought to this Shopify billing page where we have to approve the charge. In this case, the charge is nothing because it's a free plan. So we'll click approve and it's going
redirects and then we're brought to this Shopify billing page where we have to approve the charge. In this case, the charge is nothing because it's a free plan. So we'll click approve and it's going to redirect us back to our app. We can click on the generate button again and now it no longer does any redirects because the plan has been assigned to this User or the shop. We can actually confirm this by checking the database table. So let's open the users table. Let's check the plan_id and sure enough, it is set to one. We can also check the charges table. So if we open the charges table, we see that the new charge has been created for the free plan. Now to allow the User to upgrade to the premium plan and then downgrade to the free plan, that part has to be managed from within the app. I'm going to show that to you in the next episode.
from within the app. I'm going to show that to you in the next episode.
