So in order to set up Stripe, we first need to modify our schema so that we actually have some records to save once Stripe is integrated. So go inside of convex, specifically inside of schema.ts. In here we have the board schema and the user favorites joining table. Now let's add a third table here which is going to be called organization subscription. So write define table and now let's go ahead and give it some fields.
First of all it's gonna have an organization ID which is a type of string. Then we're gonna have stripe customer ID which is a string as well. We're going to have the Stripe subscription ID, which is also going to be a string. We're going to have the Stripe price ID. And we're going to have Stripe current period end.
And that is going to be date but inside of convex we write that as a number and now let's add some indexes here so we are going to add an index by organization so we can quickly catch whether some organization has a subscription or not. So for that we are using the organization id field. And then we're going to have another index by subscription. We're going to use that for when user tries to renew their subscription for example. And the field is going to be Stripe subscription ID.
So you can pause the video and simply confirm that you don't have any typos in these functions. Stripe price ID, Stripe subscription ID, Stripe customer ID, Stripe current period end. Like that. Now that you have those fields, we can go ahead and set up our Stripe util. So, let's go ahead and go inside of stripe.com and in here in the upper right left corner you can create a new account which is what I'm going to do And I'm simply going to call this...
Let me just pause the video until this loads. Okay, here we are. So I'm going to call this whatever. It can just be board additional video, for example. And I'm going to click create an account.
Let me just confirm my password here. And now that you have your dashboard here you should be in test mode and you cannot remove test mode until you activate your account and for that you need to fill out your business profile but we're not going to need to do that for this tutorial. So now just simply click on the developers tab above and in here you're going to have your API keys and we are interested in the secret key so go ahead and click reveal secret key and click again to copy it. Now let's go ahead and do the following. So I want you to have this inside of your .environment.local as stripe API key.
So paste it like that. You can use annotations like I did. And here's another place where you have to add that. And that is inside of convex itself. So let's go ahead inside of convex settings here.
Environment variables. And in here you can see that I already have this too so I'm just gonna remove everything here because you shouldn't have any of those they're like this so yours it should be completely empty So go ahead and do the following. Click add. And in here, you're going to add Stripe API key and make sure that it matches exactly what you have inside of your .environment file, or you can just copy it again, but this time copy this without annotations, right? So just the value in plain like this.
And then we're gonna add another one which is gonna be called next public app URL. And for this you're going to add whatever you are currently developing on. So in my case it's HTTP, not HTTPS, HTTP localhost 3000 and without an end slash. Like that. And you can just click save all.
And I recommend that you also add this inside of your .environment local simply so that you are aware that you have that. And let me be consistent here, I'm gonna remove annotations everywhere because I'm not using them in all places like that. Annotations shouldn't matter if that's what you're interested in but I've noticed that there are differences in Windows, Mac and Linux. Sometimes annotations do matter, sometimes they don't, but more often than not, they don't cause any problems. So ensure that inside of your convex, inside of either development or production mode, well, For development, you need to use the development mode.
So make sure that you're doing this inside of development mode here. That's very important. Go back inside of your development because you're running NPX convex dev here. And we are testing development currently. So add this environment variables inside of your development.
Great. Now that you have this, go ahead inside of the terminal here and you should run npm install stripe very simply. Now that you have stripe you can go inside of Convex again and inside here create a new file stripe.ts and Let's mark this as use node Let's import the Stripe from Stripe, which we've just added. Let's import V from convex values. Let's import internal.
Actually, we don't have any internal actions, so we cannot do that yet. But let's import action from generated server. Now let's define our URL, which is going to be process.environment, next public app URL. So make sure that you have that inside of your environment variables, inside of convex development environment right here. And then const stripe is going to be new stripe and pass in the following process.environment.stripe underscore API underscore key.
And you can put an exclamation point here to get rid of that error. And then in the second argument you can define the API version and TypeScript should auto-complete it for you. Whenever you're watching this, if you're watching in the future, it might be a different version, but the code is more likely going to stay exactly the same. Now let's create an action to open up the Stripe portal to pay. So let's write export const pay to be an action.
That action is going to accept argument of organization ID for which this subscription is being purchased. And then let's add our handler. So asynchronous function, which accepts the context and the arguments. And let's go ahead and check if we have identity so let's do await context.auth get user identity if we don't have identity we are going to throw new error unauthorized and then let's go ahead and do if we don't have arguments organization ID we're going to throw a new error organization or no organization ID And now we can go ahead and initiate our session with Stripe. So const session is going to be await stripe.checkout.sessions.create Passing the success URL to be our URL, which we've defined above here.
So after we successfully pay, we are going to get redirected back to localhost. Or in production, that's going to be wherever you deployed. The cancel URL is going to be the same thing. Now let's add the customer email, so we auto fill that. That's going to be identity.email.
Then let's add line items, open an array and inside let's define our only item which is gonna have the price data with a currency of whatever you want. So I'm gonna be using US dollars. Then let's define product data here. Let's give it a name of board row. Let's write a description to be unlimited boards for your organization.
And outside of product data create a unit amount which is going to be the price and write $2000 which represents $20. And let's write an interval to be recurring on month every month like this. Outside of price data here inside of line items add a quantity to be one and outside of the line items array, so here below that, this is very important. You need to add metadata and that metadata needs to have an organization ID of arguments.organizationId like that. And lastly let's add mode subscription like this.
Great! Now that you have that simply go ahead and write return session.url and put an exclamation point at the end so we tell TypeScript that we always have the result here. So this is the session which we've just created. The metadata is important because the payment is not something that you can capture in a promise in a sense you cannot do .then on a payment. Payments are done using webhooks so that's why we need metadata because webhooks are gonna come at an unidentified or unspecified time to our backend So our backend needs a way to know for which organization is this payment coming from because there's not gonna be any authorization from a user or something like that.
Great, so now that we have this, You can go ahead and go inside of the Pro model. So let's go Pro model here and in here you can now define that action. So let's go ahead and write the following const pay is is going to be useAction from convex-react api from convex-generated-api.stripe.pay like this. So just ensure that you've added convex-react and useAction and convex-generated-api And now let's go ahead and do the following. Let's add a state pending and set pending.
Use state. Let's define false. You can import use state from react. And then let's write const on click to be an asynchronous function. Let's go ahead and check if we have the organization ID So we can add an organization ID quickly here by extracting organization from use organization.
Let me show you where I imported this from. So clerk next JS, and let me just align this properly. So I added two of these imports, useState and useOrganization. From here, you can extract the individual organization. If you don't have organization.id for any reason, we are simply going to break the method and let's add an exclamation point here.
Otherwise, let's call setPending to be true so we can disable the button that the user is clicking. And let's open a try and catch here. Actually, we are not going to need catch. We can just do finally. And let's do const redirect URL to be await pay and inside of pay passing the organization ID to be our organization.id.
And then call very simply window.location.href to be redirect URL. And in the finally, simply add set pending to be false. And now let's use this onClick here on this button here. So I'm going to give it an onClick and let's give it a disabled of pending. So ensure that you have next public URL and stripe API key.
Go ahead and confirm inside of your terminal where you are running your NPX convex dev that there are no errors and that the last message you received is convex functions are ready and now you should be able to try this out so if I go ahead and let me just refresh this and click here, there we go, fail to create board and now it should redirect me to the Stripe checkout page. There we go. $20 per month, unlimited boards for my organization and Board Pro. If you are getting any errors it is most likely because you didn't properly add these values here so they need to be in the convex dashboard in development mode right here. Great.
So the next thing we have to do is after we pay, we need to capture that in a webhook and actually create a record in our database. Great, great job!