Premium Upgrade/Downgrade Goal0:00
Let's continue with Shopify billing and add the ability to upgrade to a premium plan and then downgrade back to the free plan. I've created a Button component to manage upgrade downgrades behind the scenes to not waste time writing React code. As you can see here we have this button placeholder which is not loading at this time because it is trying to make an AJAX call to fetch the premium status of the shop and that end point doesn't exist yet which we are going to implement. We also see that the customer slider is disabled and we have this little icon and a tooltip indicating that this feature is only available for premium plan. I basically made it so that this feature only becomes available if the user or the shop
indicating that this feature is only available for premium plan. I basically made it so that this feature only becomes available if the user or the shop is on a premium plan. So if we go to the code I also added in the FakerController to only dispatch this job if the shop is on a premium plan. So we just check if the price is greater than zero of the current plan of the user and if it is only then we dispatch this job. There are of course more efficient and better ways to manage this. You could maybe have enums or maybe abstract away the premium check into its own service class and so on.
You could maybe have enums or maybe abstract away the premium check into its own service class and so on. But I'm keeping it this way for the sake of simplicity. Now if we go to the fake data creator component we see that Axios call right here. So we're making the Ajax request to the /premium route and we get the premium status and we set that state and then the UI gets adjusted based on this state. Note that I had to install Polaris icons library to add the icon support and updated the Shopify Polaris package as well. So if you're following along make sure to run npm install. All right.
Create Premium Status Endpoint1:39
So if you're following along make sure to run npm install. All right. So let's create this route within our routes file. So let's go to web.php. We'll do Route::get('premium') and we'll make a request to a new controller. We'll call it PremiumController and we'll have a method index for this. Now let's duplicate our FakerController and create the PremiumController. So let's open the project files, we'll duplicate this and call it PremiumController. We need to import that in our web.php file and let's adjust the code in here. So first we need the index method.
We need to import that in our web.php file and let's adjust the code in here. So first we need the index method. So function index and this is going to respond with json with hasPremium attribute within the body. So we'll do return $this->response->factory->json(['hasPremium' => /* some expression */]); and we can again check if the User has premium or not by checking the planPrice so we can accept the $request in here and we can do a $request->user()->planPrice > 0. If there is no plan on the User then we'll just make this new Safe() that way it will have no premium. Let's add the json response as the return type here and let's go back to the browser.
no premium. Let's add the json response as the return type here and let's go back to the browser to test this out. As you can see in the browser we now have this upgrade to premium button. If I click on this of course it's not going to work because it tries to make a POST request to do the upgrade and we haven't implemented that yet. If we go back to the code and go to the ManagePremiumButton component we see that whenever this button is clicked it toggles the premium status and if it has premium already then it makes a DELETE request to this /premium endpoint otherwise makes the POST request to the same endpoint.
Add Upgrade POST Endpoint3:24
it makes a delete request to this /premium endpoint otherwise makes the post request to the same endpoint. So let's go to web.php file let's duplicate this twice we'll have post and delete and this will be store and this will be destroy. So let's adjust these methods now in here we don't need any of this so we'll have the shop or the user instance we can get that from the request we can change this to just request and then we can just generate the redirect the row if the user is not on a premium plan. So in here we can do a quick check if the user already is on a premium plan so we can do planPrice greater than zero and this is what I meant by maybe abstracting this.
So in here we can do a quick check if the user already is on a premium plan so we can do planPrice greater than zero and this is what I meant by maybe abstracting this away into its own Service class because we're keep on checking on this planPrice if it's greater than zero to determine if the user has premium plan instead you could have maybe a separate Service class that determines that but for our case this is simple enough. Let's make this null safe as well if user already has the premium plan then we'll just return this responseFactory json without any content. Let's change the responseType to JsonResponse and we need to get the redirectUrl so let's actually copy the redirectUrl from our BillableMiddleware. So we'll scroll down here.
actually copy the redirect URL from our billable middleware. So we'll scroll down here. Let's copy this and put it in here again this can be extracted into its own service method or something but we're just going to keep it this way. Now the reason we're using the same URL is because this route actually supports the plan ID in the route path so we can actually pass some kind of plan ID here like this and is going to generate the proper URL to redirect and select that plan. If we search for this route we see that we have that within the shopify.php and if we open that we see that the route has optional plan in the route path. So if we don't pass any plan ID then it just selects the default plan by default which
we open that we see that the route has optional plan in the route path. So if we don't pass any plan ID then it just selects the default plan by default which is set to on install in our table. Otherwise if he has the plan ID in the route path then it will use that plan. So let's close that out and we need to figure out how we can pass this plan ID in here. We actually don't need anything from here. Let's get rid of that and to get the plan ID we can basically query the plans table and select the plan that has price greater than zero. Again this will depend on your implementation and how you do the billing. You may have plan selector within the front end and have the plan ID attached to each.
Again this will depend on your implementation and how you do the billing. You may have a plan selector within the front end and have the planId attached to each div component or something like that. That way all plans are visible to the user and whichever button is clicked it will generate the URL based on that planId which would be either part of the button attribute or the div attribute or whatever component you use. So let's do it this way for now we'll do $planId = Plan::query()->where('price', '>', 0)->first()->id this way. Let's make this null safe because we may not have any plans that have price greater than zero.
Let's make this null safe because we may not have any plans that have price greater than zero. If there is no plan ID we can throw some kind of exception here either the ValidationException or RuntimeException that's up to you. We'll throw RuntimeException for now we'll say no premium plan found. All right so now that we have the redirect URL here we can return the JSON response with redirect URL in its body. Again the front end takes care of the actual redirect if we inspect the manage premium button here we see that if the response contains that redirect URL it's going to do the redirect. So let's test this out.
Test Upgrade Billing Flow7:12
button here we see that if the response contains that redirect URL it's going to do the redirect. So let's test this out. Let's go to the browser. Let's click on the upgrade to premium button. It's going to do some redirects here and it's going to take us to that Shopify billing page to accept the new charge. As you can see we have the new plan called premium with the $2.99 every 30 days. We can approve that. Let's approve and it's going to take us back to our app. Now as you can see the number of customers is available and we can slide it and we have
Let's approve and it's going to take us back to our app. Now as you can see the number of customers is available and we can slide it and we have the button that says to downgrade back to free. We should probably get rid of this star icon if we are on premium. So let's do that quick. We're going to open the FakeDataCreator component scroll down. This range slider has the label of customers label. So let's inspect that. We are showing that premium icon right here with the tooltip. So in here we can do something like if hasPremium then we'll just show no otherwise
Implement Downgrade Endpoint8:08
We are showing that premium icon right here with the tooltip. So in here we can do something like if hasPremium then we'll just show no otherwise we'll show this tooltip with the icon. Let's format the code save. Let's switch back and sure enough that little star icon is gone. Now let's implement the downgrade option because if I click on this this is not going to work currently because we haven't implemented that endpoint. Let's go back to code. Scroll down to the destroy method and we actually are going to do the same thing that we were doing here.
Scroll down to the destroy method and we actually are going to do the same thing that we were doing here. So let's copy all this and paste it in here and we're going to adjust it so that if the user is on a free plan then we don't need to do anything. So we can do something like if the plan price is less than or equal to zero then we know that the user is on the free plan. Either the price is less than or equal to zero or the user has no plan. In that case we'll just return the empty JSON. So we'll do json response in here and again this can be a validation exception if you wanted to.
So we'll do JSON response in here and again this can be a validation exception if you wanted to. Then in here we don't actually need to do this because we're downgrading back to the initial plan that the merchant is presented with when they install the app. That's the one with the onInstall set to true in our plans table. So if we get rid of this and get rid of that this is actually going to take care of that for us because Laravel Shopify behind the scenes selects the plan that has onInstall set to true and that's the initial plan. And in our case that's the freePlan and again this will depend on your specific implementation but for us that's the freePlan.
And in our case that's the free plan and again this will depend on your specific implementation but for us that's the free plan. So we don't need to specify any plan ID. This will automatically generate the proper URL for the free plan. So let's go back to the browser. Let's try to downgrade now to free. It's going to do again the redirects and bring us back to the billing page. We're going to approve the new subscription which is the free and it's going to bring us back to our app with the customers option being disabled and a button to upgrade to premium.
Wrap Up and Next Topic10:10
us back to our app with the customers option being disabled and a button to upgrade to premium. Now as you can imagine in a real application you probably would have some more options maybe some kind of pop up listing exactly what the merchant would be getting for the premium plan and maybe even have multiple plans that they can select from. Alright so now that we have the billing out of the way let's move on to webhooks in the next episode.
