This episode is for members only

Sign up to access "Laravel Subscriptions" right now.

Get started
Already a member? Sign in to continue
Playing
26. Adding lifetime memberships

Transcript

00:00
In this episode, we're going to add the ability for customers to sign up or swap to lifetime memberships. There's quite a lot of work to do here.
00:08
This is both a really easy thing to do, but also a pretty difficult thing to do with making this play nicely with cashier. I'm going to show you the way that we do this on code course, and it works really, really nicely. And it's pretty straightforward. Okay.
00:22
So there's quite a lot to do here, but the first thing that we're going to start with is just making sure that everything is cleared out. So I've gone ahead and cleared out the Stripe ID for the user that I'm signed in for. I've also gone ahead and gotten rid of all of these subscriptions and subscription items just so we can start fresh. And the first thing that we're going to do is create our own migration to add a column in the user's table, which marks them as a lifetime member or not.
00:48
So it's just going to be a basic boolean. Let's go ahead and do that first, and then we'll swap over the entire application so we can allow access if that flag is set to true. Okay. So really simply, we're just going to go ahead and make out a migration here, and we're going to add a lifetime membership flag to the user's table.
01:08
Okay. Let's go ahead and open this up. So add lifetime membership to user's table, and let's just build this in. So let's add in a boolean here.
01:17
You could also add in a timestamp if you wanted to work out when this was added, but you'd have all that information anyway. So a boolean works fine. So let's add in a lifetime membership in here and let's add in a sensible default or false. Before we forget, let's help over to our user model and we'll just make sure that we add this to our fillable field so we can update it.
01:38
Or of course, just set this as unguarded. So this will be lifetime membership and we're good. Let's go ahead and migrate what we have here. And that's now in there.
01:47
Great. Okay. So let's come over to the database and for this particular user, let's go ahead and just set that to true or one. And we want to get to the point where we can access this protected area or see details of our membership under our subscription section here, if we have a lifetime membership.
02:06
So the first thing to do is figure out whether or not we should override any of the methods over on cache here. So we know that we have that billable trait. What we could do is we could come into the manager's subscriptions trait. We could take these subscribed method and we could override it and start to add in our own methods here.
02:24
But I found in the past, this gets a little bit tricky, especially if there are any updates to cache here. So what we want to do is create our own method over on the user model, but utilize that subscribed method to check if the user has either a subscription or a lifetime membership. So we're going to keep the subscribed method completely separate from our own check for lifetime membership. Okay.
02:49
Let's get started with a really simple has lifetime membership method in here, which because we're just using a Boolean is just going to go ahead and return this lifetime membership. And that's pretty straightforward. So we can use that method anywhere in our templates or our controllers. Next up, we'll go ahead and create out a has membership method.
03:12
Now this is going to encompass both a subscription and a lifetime membership, but because we want to use this and replace out anywhere that we've used the subscribed method, this is going to need to have the same signature. So if we open up the manager's subscriptions trait and look at subscribed, we want to take this same signature. So it works the same as subscribed, but also takes into account the lifetime membership. So the first check in here is going to be, if the user has a lifetime membership, then we're going to return true.
03:43
So let's just go ahead and say, has lifetime membership, that method that we've just implemented and let's return true here. And then what we can do is just go ahead and use the subscribed method as normal. So let's pull that in from that trait, passing in the type, passing in the price that we could potentially check and we'll return true here as well. By default, we'll return false.
04:03
Of course, if they don't have a membership. Okay. So now that we have this has membership method, we can do a find and replace in all of our files where this exists. So let's go into just the has the Laravel subscriptions directory.
04:18
Let's find out where we've used subscribed and let's replace this over with has membership. So let's look at where we want to replace this and where we don't. So over in the blade directive that we created earlier, we do want to replace it there. So let's go ahead and include that.
04:35
Inside of the subscription controller, where we're retrieving the details of the plan, we don't want to include it here because this is only for a subscription. So we'll ignore that one over in our middleware. We do. This is probably the most important part because the middleware catches if we don't have any kind of membership at all.
04:52
So we're going to go ahead and replace it out in both of them. And we don't want to replace it out in our model because that's where we're using this here. And we don't want to include it in these templates here because these specifically relate to subscriptions where we're showing the user when their plan renews or if they've canceled it. You can't cancel lifetime membership and there are no upcoming charges to lifetime membership.
05:13
So we can leave that there as well. Okay. Let's hop over to the browser and you can see already the plan section has disappeared and we can access the protected area. So this looks like it's working really nicely.
05:24
Let's go over to our subscription section and see what we've got. And you can see we've got a little bit of work to do here. So we've successfully updated this. So the user can access any protected areas using that new method and the middleware that's kicked in.
05:36
Okay. Let's update the subscription section here with these subscription details. At the moment, we've got this choose a plan button. So it looks like we don't have a membership.
05:45
We basically just want to swap that over to tell the user they have a lifetime membership. There's no real action they need to take if they have a lifetime membership. There's no action to cancel it or resume it or anything like that. So let's hop over to the subscription detail section where we have this choose a plan.
06:02
And actually we could use if they don't have a membership here. So that kind of works. So we do want them to choose a plan if they don't have a membership. So we'll swap that one over as well.
06:12
And now we end up with a completely empty space. Now, what we can do is just add a little if statement somewhere, because these relate to actual subscription and just tell the user they have a lifetime membership. So in here we could say auth user has a lifetime membership and the if statement out there, and we can just say something like you have a lifetime membership. And of course you can add anything else you want in there.
06:36
So as it stands, we've successfully swapped over the ability to check if the user has a membership, which includes a lifetime and a subscription type as well. So everything is now working as we would expect, and you can just add anything you want in here. Okay. So the next step is to look at checkout.
06:54
So let's go back over and set our lifetime membership flag back to false. So next job is to look at the checkout flow. Before we do anything, let's go ahead and create out a new product in here. So we'll go ahead and add in a product for our lifetime membership.
07:09
Let's just call it lifetime and let's choose one off. Of course, we'll change the currency over so it matches and we'll go ahead and set the price. So let's add that product. We'll come back to this later when we add this to our config.
07:21
But we've now got that product set up. Okay. Let's go and just add a new plan to here that we can eventually click through to. So if we go over to the plans view, let's just copy one of these down here.
07:35
Go ahead and open this up and just change the details over here to lifetime. Let's go ahead and set this to the price that we set. And we'll set that to once. Not sure where this is going to go through to yet, but we now have the lifetime option on here.
07:50
Okay. So now over to our config. So let's open up our subscriptions config and let's think about what we need to do here. So we're going to put this under plans, but we're going to add an additional type to each of these.
08:02
So let's just say that this is recurring. And let's do the same thing for yearly as well, just so we can differentiate between the two. And we're going to say lifetime and we can set exactly the same properties on here. So let's give this a title.
08:17
We'll set a price ID from Stripe, which we'll grab in a second. And we'll set the type here to just once, or you can call that whatever you want. Let's actually call that lifetime. It kind of makes sense.
08:27
Okay. So we'll grab the price from Stripe. So let's pull in the price for this. And that's going to work in exactly the same way.
08:34
The checkout flow will just be a little bit different. And now we have this in here. So now we can come over to our routes. We're going to create out.
08:43
There's a couple of ways that we could do this. We could create out an entirely new route, or we could create an if statement within the index method of this checkout controller. I'm going to split this up into two routes.
08:53
Just makes a little bit more sense. So let's split these up. Let's call this lifetime. At the end.
09:00
And let's call this lifetime and we'll call this recurring. So we'll switch this over just so it's a, makes a little bit more sense. And we'll add a lifetime onto the end of here. So now that we've got these two routes, let's change this over to recurring with this.
09:15
We now want to add an additional where clause onto here because we want to check the plans where the type is recurring. So remember they're all lumped together now. So when the plan gets passed down into here, we want to make sure that that's a recurring plan.
09:31
Okay. So none of this changes. Let's go ahead and create out that lifetime method in here. And again, we'll bring our request directly into here and we can pretty much do the same
09:44
thing with this abort unless. So let's go ahead and pull this down into here and we could even just pluck the plan out, assuming we're only ever going to have one lifetime plan. So let's go into our subscriptions plans where the type is lifetime and we'll grab that plan
10:02
out by its key of lifetime. You don't necessarily need to do this, but let's go ahead and roll with that. So let's go ahead and die dump on our plan to see if that is successful and let's hook this up over in the plans view.
10:16
Okay. So we're going to go through to checkout and lifetime and we'll set the plan here to lifetime. The only reason I'm doing this is you might have multiple lifetime plans that offer
10:26
different benefits, or you might just have lots of different prices. Okay. Let's go over to our app and see what this looks like. So let's click on lifetime and there we go.
10:36
So we've now got the price ID, which we can pass through to Stripe to checkout. But how do we do this? Let's go over to our checkout controller. And to be honest, it's pretty much the same as what we've done here, but we just don't
10:49
create a new subscription because obviously it's a completely different type of payment to one-off payment. So let's go ahead and return the currently authenticated user. It's going to need to be authenticated to do this.
11:01
We can do exactly the same thing, like allow promo codes or any other of the methods that Cashier provides, but then we just want to call checkout. Like I said, we want to pass in the price ID. So let's pass in the plan price ID.
11:15
And then once again, we want to pass in exactly the same payload here. So let's grab the payload that we have here. I'm actually going to get rid of this email because that's sent implicitly. And let's go ahead and just paste this payload into here.
11:28
If your payload is any more complicated than this, I'd recommend creating out a separate method in here with the payload that you can use to just reference between both the subscription and the lifetime methods. But since ours is pretty simple, we can just leave it how it is.
11:43
Now, one thing about this is when we do go through to checkout, this isn't by default going to generate an invoice for us. So what we can do is we can pass the invoice creation options directly to the Stripe API, and we could set enable to true.
11:57
That will create an invoice over on Stripe, and then we'll be able to see it within our own dashboard. Okay. Now we've done this, let's just go through and see what happens.
12:05
So let's go and click on the lifetime plan and we should be forwarded directly over to Stripe with the price here, the ability to add a promo code, and of course, the ability to add in our card details, so we could go through and make a payment here, but nothing much is going to happen.
12:21
This will take the payment, but we don't have a webhook event enabled or a listener inside of our app to update the user's account with that Boolean lifetime membership set to true. So let's go and first of all, add the event to our webhooks that we have.
12:37
So let's head over to the developer section over to webhooks and let's update the webhook that we generated earlier with cashier. Okay. So the event that we're looking for is checkout session completed.
12:49
Let's go ahead and add that into our list of events and update the end point. And now we can go ahead and create a listener to handle this. So over to our app, let's go and make out a listener in here. We're going to call this handle lifetime membership.
13:04
So let's open up our listener section and we have the handle subscription created and ended send emails. Okay. So under handle, we're going to pull in the webhook received event again.
13:15
And inside of here, let's just log something. And we'll log out the event payload just to make sure that this is working. We'll go over to our Laravel log and clear this out. And let's just check that this is working and just make sure that you have your
13:29
local tunnel set up to receive these. Okay. Let's go over and make a payment here and see what happens. So I'm going to enter all of the details again.
13:38
And let's go ahead and hit pay here. Okay. Okay. So that payment's gone through.
13:44
Obviously nothing has happened in our app yet, but let's open up our Laravel log and see. So we're getting this through now. Now, the one thing that we need to be careful about is the
13:53
type of payment this relates to. If we go ahead and search for mode here, we can see that we've got a mode of payment. This event that we've added to our webhook will also be sent for subscriptions as well. Now, the last thing that we want is a user signing up for a monthly subscription,
14:10
receiving this event, this webhook event through, and then going ahead and upgrading them to a lifetime membership. So what we want to do is check that the mode is payment, as well as checking inside of this listener that we're dealing with the correct webhook event.
14:25
So let's go ahead and handle both of these now. So let's go and create our if statement in here. We'll do these both in line, but you could split these up. So we're going to say event payload and type, and we want to make sure the type
14:37
is the event that we're actually listing for. So that is checkout session completed, much like we did with the others. And we're also going to say the event payload data object and mode is subscription. So actually what we're saying is if it's not this webhook event that we're
14:58
listing for, or we're dealing with a subscription, then we want to do an early return. Okay. So now down here, what we can do is find that user and upgrade them. So we know that we have completed the checkout session.
15:12
It's not a subscription that we're dealing with. So it must be a lifetime membership. Let's go ahead and find that user. And we can do that again with the cache if find billable method.
15:23
Let's go ahead and find that user by the event payload data object and customer. So that'll plug them out by their Stripe ID. By the way, at the point that we checked out and went through to the checkout, that will have created a Stripe customer for us, much like when we handled subscriptions.
15:46
Okay. So we found the customer. Now, what we want to do is go ahead and update them. So let's go ahead and update the lifetime membership column.
15:56
And we will set that to true. Okay. So with this done now, whenever we receive a payment and it's not a subscription, we're going to assume that they've purchased a lifetime membership.
16:05
There are a couple of other checks that you can add in here. You could check the price ID to make sure it matches the price that you were expecting. You could check the amount if you wanted to. We'll leave it like this for now.
16:16
And then you can go ahead and tweak this. Okay. So now that we've got this working, let's go and just clear out our Laravel log, just in case we have any issues and let's go over and try this out again.
16:26
Okay. So we're going to make sure that lifetime membership is set to zero and we're going to go through and purchase a lifetime plan. Okay.
16:34
I'm going to go ahead and fill these out again. And once we've done this, let's go ahead and hit pay. Okay. So once this goes through, that should send that webhook event and find us by our
16:43
customer ID, and it should go ahead and upgrade us. And there we go. We now have a lifetime membership. And if we come over to our subscription section, there we go.
16:52
So really easy. We've now gone through the checkout process to upgrade the user to a lifetime membership. There's nothing else that needs to happen here.
17:00
And you can see that we've got our two invoices because of course we made that test payment before. So we can download the invoice as usual. Okay.
17:07
So aside to any tweaks that you personally want to make to this, that is pretty much the process of getting a lifetime membership set up, but there's one thing that I want to focus on as well. And that is swapping from a normal plan to a lifetime membership.
17:21
So let's go over to the database and I'm going to get rid of the lifetime membership or set this back to zero. We'll keep the same customer here, but I'm going to go ahead and sign up for a standard subscription first and then swap over to lifetime.
17:34
So let's go ahead and purchase a monthly plan here, enter our details. And once we've done that, go ahead and hit subscribe. So that's obviously going to subscribe us and create a plan out. And now that we've done that, we're into the standard way of seeing our
17:49
subscription, so everything that we've already covered here, what we're going to do is add another button here to go through to swap, because at the moment due to our middleware, we can't access the plans page and that's the way that we want to keep it.
18:02
So let's go over to our details section and let's go to the manage your subscription section and just go ahead and copy this down and create another button and of course the UI you can update to whatever you want. So I'm going to say swap to lifetime and we're going to go through to the checkout
18:19
lifetime endpoint that we created earlier. So now if the user has a standard subscription, they can also click to go through and swap to a membership. However, this will not cancel that original subscription.
18:33
Let's go over to Stripe over to our customer section and you'll see here, of course I have an active subscription. What we want to do is we, when we go through and actually pay for a lifetime membership, we want to immediately cancel the user's subscription if they have one.
18:48
So let's go back over, we'll go ahead and update our listener. So let's go over to handle lifetime membership. And down here, we just want to add a simple if statement to check if the user already has a subscription and then cancel it immediately.
19:00
So we're going to go ahead and say, if the user has a membership, or you could say subscribed, because if they have a lifetime membership, we don't really care about that at this point, only if they have a subscription. So if they are subscribed, we want to access their subscription.
19:15
So let's go ahead and pull in their subscription here. And we don't want to say cancel because that will cancel at the period end. Ideally, we just want to immediately cancel it. So we just want to cancel it straight away.
19:26
Okay. Let's go ahead and try this out. So let's go over to swap to lifetime and we'll keep an eye on Stripe. Let's enter our card details.
19:35
And once we've done that, let's go ahead and hit pay, and that should cancel that user's subscription. Okay. So we are back through, of course, we still have a subscription.
19:44
If we head over to the subscription section, this is now switched over to lifetime membership. Our invoice is available there, but more importantly, that will have canceled fully the customer's monthly or yearly plan.
19:57
Okay. So there's a ton more that you can do this to tweak it. However you want this to work, but that is the general idea of setting up lifetime memberships within our subscription platform.
26 episodes2 hrs 38 mins

Overview

Learn to start accepting subscriptions with Laravel and build a solid foundation for your next application. In fact, it’s exactly how we do things here on Codecourse!

Even if you’re completely new to Laravel, by the end of this course you’ll have a platform where customers can purchase subscriptions and gain access to restricted areas of your application.

We’ll also cover managing subscriptions, cancellations, invoices, giving customers trial periods — and more.

Alex Garrett-Smith
Alex Garrett-Smith
Hey, I'm the founder of Codecourse!

Episode discussion

No comments, yet. Be the first!