Adding Connect with Stripe to a React App
We'll switch over to work in our React application and have it communicate with the server to allow a user to connect with their Stripe account on our platform.
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
This lesson preview is part of the TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two course and can be unlocked immediately with a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to TinyHouse: A Fullstack React Masterclass with TypeScript and GraphQL - Part Two with a single-time purchase.
[00:00 - 01:58] With the mutations now available in our server to allow the user to connect or disconnect with Stripe, we'll prepare the client-side functionality to give the user the capability to connect with Stripe through the UI. Our objective would be to surface a connect Stripe button in the user profile of a logged in user, where when the user clicks disconnect button, they're taken to the Stripe login page, where they're able to log in and activate their Stripe account on our tiny house Stripe account platform. And when the user logs in and activates their account, they'll be redirected to the /stripe route of our application, where we'll render a Stripe component that will simply receive the code query parameter from the URL, run the connect Stripe mutation and pass this code value along. When this connect Stripe mutation completes successfully, it means we would have been able to grab a Stripe user ID for this viewer and store it in the viewer document within the user's collection. By then, we'll simply redirect this viewer to their user page. When successfully connected with Stripe, instead of seeing the connect Stripe button, they'll see the income they've made, which is a value of the user data object. And there'll be a disconnect Stripe button that when clicked will simply disconnect this viewer by removing the wallet ID value in their user document in the database. And we'll bring back the button to connect with Stri pe. We'll take this step by step. In this lesson, we'll focus on creating and conducting the connect Stripe mutation. So the first thing we'll do is we'll create the connect Stripe mutation documents in our client projects. We'll head over to the lib graph ql mutations folder, create a connect Stripe folder that is to have an index file.
[01:59 - 03:13] And in the lib graph ql mutations index file, we'll re export the soon to be created mutation documents. In the connect Stripe index file, we'll import the gql tag from a polar boost, export a constant labeled connect Stripe, specify the connect Stripe mutation, declare that it expects an input argument of graph ql type connect Stripe inputs, and we'll declare the mutation field and pass that input along. What will we need returned from this particular mutation when successful? On the server, we highlighted that this mutation will return information such as the viewer ID , the avatar, etc. However, at this moment, we're only going to be needing the has wallet field that is going to be returned. If you're confused as to what this has wallet field is, we'll take a very quick brief look at the resolver function in our viewer res olver's map.
[03:14 - 04:36] And we can see that when we connect as well as disconnect with Stripe, we've said that we're going to return the wallet ID field from this root level mutation. However, we don't trivially return this wallet ID field next. Instead, what we actually have is another res olver that we've created before called has wallet that simply checks for the presence of this wallet ID. So the viewer object that is essentially returned from graph ql doesn't have a wallet ID field, but it has wallet boolean. The reason being, on the client, we don't actually need to know what this wallet ID value is. I don't think it's supposed to be a secret, but it's just not necessary for the context of our client application. When we build out the capability to have payments be facilitated when a booking is made, that will be handled on the server side. So on the client, all we need to know is does this user have a wallet ID, with which we return this has wallet value to determine this. With the this graph ql mutation now defined in our client, we'll auto generate the corresponding TypeScript definitions. So we'll head over to the terminal, and in our client project, we'll run npm run code gen schema to generate a schema JSON file.
[04:37 - 05:24] And when complete, we'll run the npm run code gen generate command to generate the TypeScript definitions for our new mutation. When we take a look at the user profile section of a logged in user, we'll see that we've already established the UI with the button that will allow this logged in user to connect with Stripe. So step one would be when the user actually clicks this button, we want to take them to the Stripe login page for our connected platform account. In the Stripe documentation, they note that this OAuth link can be created as follows.
[05:25 - 06:10] HCTPS connect dot stripe dot com slash OAuth slash authorize and with a few necessary query parameters. Response type should be code client ID should reference the connected accounts client ID with which we've stored as an environment variable in our react application and scope with a value of read, write, we'll share a link to this documentation in the lesson manuscript as well. So with that said, in our user profile component file outside of our user profile component function, we'll create a constant called Stripe Auth URL. That will be the URL we just saw.
[06:11 - 06:56] However, for the client ID, we'll specify a value of the client environment variable we have called react app s client ID. When the user clicks the connect with Stripe button, we'll want to direct the user to the route we've specified in our Stripe Auth URL constant. We could think of trying to use the link component from react router. However, in this context, we're not linking the user to a route within our app, but instead to somewhere else completely different.
[06:57 - 08:55] So what we can do is in our connect stripe button is have an on click handler that will call a method called redirect to stripe. And the redirect to stripe method will simply use the window object available and set the location target to the value of the Stripe Auth URL constant. So let's now try this out. We'll go back to our application and we'll try to click the connect with Stripe button. By doing so, we'll notice that we're redirected to the login page for our Stripe Connect platform. We see the logo and the tiny house name we've set up as the business for this page. Notice that I'm logged in as my personal stripe account. You may be prompted to log in or sign in with an actual stripe account before you see this connect page. If you do and are prompted, I recommend that you actually create a personal stripe account to play the role of a user logging in and connecting with Stripe in our application. For real live payments, the user or myself here attempting to connect and be a connected user in the tiny house platform will need to activate their account here and provide information about themselves and their business to be able to process payments with Stripe. Since we're building this in development and we're using test credentials, we can click the skip the account form action available at the top to help skip this form and pretend that we've activated our account. And by doing so, we'll notice that Stripe returns us to the redirect URL that we've set up, slash Stripe. And it has query parameters of scope read writes. But more importantly, we have the authorization code returned to us from Stripe.
[08:56 - 09:40] This is where we need to have functionality that will receive this code and trigger the connect stripe mutation we have in our server and pass the code argument along. We saw the not found text be displayed to us in the slash Stripe route, since we don't have an appropriate component that should be shown yet. We created a login component before that allowed the user to begin the login process. However, it was also responsible in being the redirect URL and consuming the code returned from the Google sign in OAuth process. We'll create a Stripe component that is pretty much responsible in just doing the latter part of what we mentioned.
[09:41 - 10:08] It won't really display anything to the user, but instead it will be responsible in receiving the code calling the connect Stripe mutation. And when successful, it will take the user to their user page. So the first thing we'll do is we'll create a Stripe section folder that is to have an index.tsx file. And in the sections index file, we'll re export the soon to be created Stripe component.
[10:09 - 11:22] Let's import the majority of things we'll need for this Stripe component. We'll trigger the connect import the react Stripe mutation when this component first renders. We'll import the use mutation hook from react Apollo. We'll import the layout and spin components from ant design that we'll use to help display a loading spinning indicator when the mutation is in flight. We'll import the connect Stripe mutation and its auto-generated TypeScript definitions. We'll destruct the content component from layout and we'll export the Stripe function components.
[11:23 - 12:45] We're going to need to run the connect Stripe mutation. So we'll use the use mutation hook, pass in the auto-generated types for the connect Stripe mutation, pass in the mutation document, return the mutation function, and we'll also return the data loading and error statuses of the mutation results. We'll want to run the connect Stripe mutation when the Stripe component first renders. So we'll use the use effect hook and specify an empty dependencies list since we don't picture running this effect more than once. When we plan on running the connect Stripe mutation, we'll want to pass in the code retrieved from the URL parameter. To access this code, we did something in the login component page by using the URL constructor and retrieving the value of the param labeled code. We'll do the same in our Stripe component, so we'll copy that over. With the this code available, we'll say that if this code exists, we'll run the connect Stripe mutation, provide the input object argument that it expects, that is to further contain the code property.
[12:46 - 14:26] The use effect hook now warns us that we should place the connect Stripe mutation function as a dependency, since there's a risk for it to change in value. This connect Stripe mutation is being instantiated within our Stripe component. If the Stripe component goes through another render, we'll get a new copy of this connect Stripe function. And if we place it as a dependency, we'll have the risk of having this effect run again. We definitely do not want that. In this case, we have a strong opinion of only wanting this mutation to only run once. So with that said, we'll import the use ref hook from react. And in our components, right after we obtain the connect Stripe function, we'll create a reference object for dysfunction. So it won't have a change in value through the life of a component, unless we specify that change. And we'll use the reference constant and access the current property, which would exist when created with the use ref function, which will be the connect Stripe function itself, that will now remain unchanged for the life of the component, unless we explicitly change it. Before we handle how this Stripe component should behave, when this mutation is successful, we'll handle the other situations. For example, when the connect Stripe mutation is loading, we'll want to show a loading indicator. So we'll check for the loading status of the mutation result, and we'll say when it's true, we 'll display the content component and the spin component within that says connecting your Stri pe account.
[14:27 - 15:26] What would we want to do if our mutation error out? If our mutation was to error out, we'll simply want to take the viewer to their own user page, and we'll look to display an error message that says something went wrong, and they couldn't connect with Stripe. There's a few things we'll need to have this happen. First, we need the capability to redirect the user. There's many ways we can have this redirect to happen. For example, in the on error callback within our mutation result, we can maybe use the history object that will be available in this component to make the redirect. However, since we'll use the error result from the mutation, we'll look to employ the redirect component from React Router. So let's import the redirect component from React Router down.
[15:27 - 15:57] And in our component function, we'll say if error is ever true, we'll redirect the viewer to their own user page. If we recall, the user route expects a URL parameter of the user ID. We want to go to the user page of the viewer, the person viewing the app. So we'll need the ID of the viewer.
[15:58 - 16:14] When we are to have this Stripe component rendered in the parent app component, we'll pass the viewer object available down as props. So we'll state that the Stripe component expects a viewer prop. We'll import the viewer interface we have in the libs types file.
[16:15 - 16:41] And we'll say the prop is to have this type. And finally, we'll say the component is to expect this viewer prop. In our redirect statements, we'll say the URL parameter is the ID of the viewer. Great. However, we don't simply want to take the viewer to their user page.
[16:42 - 17:07] We also probably want to tell them that something might have went wrong. A simple way we can achieve this is appending a query string or query parameter to this redirected route. We can say something like Stripe error equals true. This Stri pe error query string will only exist if the viewer attempted to connect with Stripe and for some reason or another, it fails.
[17:08 - 18:09] We can take a quick tangent to the user page or the user components for the user page and try to handle what would occur when this query string Stripe error exists. Somewhere in the components function, we can attempt to access this query string with the help of the URL constructor, like we've seen before. And we'll keep this in a constant that we'll call Stri pe error. Now, as a quick note, we probably mentioned this before, but React Router doesn 't provide a way to access query strings within URL routes, which is why we're going with this more manual approach of using the URL constructor to obtain the query string parameter. We already have the error banner component imported. So what we can do is we can check and see if this Stripe error constant exists, a Stripe error banner constant element will be the error banner with a message that says we had an issue connecting with Stripe. Please try again soon.
[18:10 - 18:33] And in our return statement for our user component, we'll simply place the Stri pe error banner constant element. Now, if I was to visit the user page of my own profile, it should behave as normal. However, if I was to place a query parameter of Stripe_error=true, we'll see the error banner.
[18:34 - 19:17] Great. Now, once again, we only would have this Stripe error query banner within the URL when the connect Stripe mutation errors and the user is redirected to this user page. Okay. So let's go back to working in our Stripe component. When data from our mutation result exists and the connect Stripe object within the data return is present, we'll want to redirect the viewer to their own user page as well. So similarly, we'll place an if condition to say if data and the connect Stripe object within data is present, we'll redirect the viewer to their user page, but this time without the Stripe error query parameter.
[19:18 - 20:55] And if data isn't present, loading isn't happening and no error exists from the mutation, this is unlikely, but we'll have our components just return null. This is primarily how we'll want the Stripe component to behave, except there's a few more other things we'll look to handle. When the mutation is successful, we'll look to update the viewer object available in our client app. To notify the fact that the viewer now has wallet information, or in other words, the viewer object has the has wallet field as true. This is important since later on, we'll want to restrict the viewer from certain actions only until the wallet is available, or in other words, the user or the viewer has connected with Stripe. So we'll need to ensure this viewer object state object is up to date within our clients. Since we want to run this effect or function when this mutation is successful, we can try and do this within the on completed callback function from the use mutation options. The parameter will be the data object returned from the mutation. If we want to update the viewer state object in the parent, we'll probably need to use the setViewer function that should be passed in as props. So let's say that this Stripe component would expect a prop called setViewer, which is a function that expects a viewer parameter and returns void. And in the on completed callback, we'll use the setViewer function.
[20:56 - 21:33] Note that the object we return from the connect Stripe mutation isn't the entire viewer object within the context of the client, but just the has wallet field. So we'll use the spread operator to take all the fields of the viewer object passed in as props, and simply look to update the has wallet field with the value obtained from the mutation. And though I don't think we need to check if data and the connect Stripe object within data exists, since I believe this on completed callback will run when the mutation is successful, we'll still add this additional if statement and check.
[21:34 - 22:21] The other nice to have thing would perhaps maybe involve telling the viewer through a success message that they've connected with Stripe successfully. We have a display success notification function in the libutils file that we can use for this. So with that said, in our Stripe components file, we'll import the display success notification function from the file in libutils, and in the on completed callback, we'll run the display success notification function. We'll state a title of you successfully connected your Stripe account.
[22:22 - 22:48] And a description that says you can now begin to create listings in the host page. In the next coming modules, when we build out the capability to host a listing, we'll only allow the users or viewers that have connected with Stripe to be able to create a listing, which is why it'll be useful to tell them now that, hey, you can create listings.
[22:49 - 23:42] The one last thing we'll look to handle is the fact if the user tries to access the Stripe routes, and there's no code query parameter. And this can happen if the user tries to hit the Stripe route directly in the URL bar. At this moment, I assume nothing would really happen. And the user might just see a blank page. But let's look to avoid this, because at the end of the day, this Stripe component only holds the responsibility to simply receive the code and run the mutation. So we'll say if the user tries to access the Stripe route, and there's no code in the query parameter, we'll just take them directly to the login page. We'll say the login page to be on the safe side since the viewer may not be logged in at this moment, so we can't take them to their own viewer page at all times. We'll want to handle this in the use effect hook itself.
[23:43 - 24:13] We shouldn't and probably can't use the redirect component in the use effect hook since the use effect hook is supposed to run side effects. It isn't supposed to render JSX. Since this Stripe component will be rendered as a routes level component in our app, we'll have access to react router's history object as a prop. So let's import the route component props interface from react router. And we'll use it and say the component is to expect the history object.
[24:14 - 25:08] And in the use effect hook, if the code doesn't exist, we'll use the replace function from the history object to replace the route path name with slash login. We'll add the history object as a dependency since we won't foresee it changing and causing our effect to run more than once. By using the replace function in the history object as opposed to the push function, we replace the browser entry from what the route would have been slash Stripe to slash login. This would help avoid the fact that if the user clicks the browser back button, they wouldn't go back to the Stripe route. They would go back to the route before that. This is because the replace function simply replaces the entry as opposed to pushing a new entry to the browser stack. And that's it.
[25:09 - 26:09] This is our Stripe component. And the parent app components will import the Stripe component from the sections folder. And in our return statement, we'll look to have the Stripe component be returned for the slash Stripe route. Similar to how our login and user components use the render props pattern, we'll do the same to render the Stripe component and pass the viewer and set viewer props that it is to expect. Amazing. Let's see now how our Stripe functionality works . We'll go to the user profile of the user page. And we'll attempt to click the connect with Stripe button will be taken to the login activation for where we're supposed to activate our Stripe account. In development, we can skip this. And then we'll be taken to the slash Stripe route, where we see the loading indicator telling us that we're connecting with a stripe.
[26:10 - 26:38] When that successful, we'll be taken to our user page, where we get the notification that we've connected with a stripe. Great. And at this moment in time, if I was to look at the wallet ID value for the user document for my profile in MongoDB Atlas, I'll see the wallet ID value populated. This is the Stripe user ID of the connected account in our tiny house stripe accounts.
[26:39 - 28:19] And if we go to the Stripe dashboard for our tiny house app, we'll see one connected account in our connect platform. It tells me who disconnected account is the email. The fact that it is restricted, which is fair, because we haven't provided any appropriate information. We just skipped the activation form. It also tells us the total pending and current balance for disconnected accounts, which I find super interesting. Now keep in mind that this is a test data. I don't have this balance my stripe account. It also tells us that the date the connection was made. At this moment, if we keep connecting, we'll go through the flow is normal. We'll connect and when the mutation is run, it will store the stripe user ID as the wallet ID of the user document in our database. The user can connect with different stripe accounts, but every time they do so, they'll update the wallet ID value of their user document. So at the end of the day, we'll take the most recent account that's been connected. Amazing work. In the next coming lesson, we'll update the UI for this user profile to instead show the total income they've made and a button to help disconnect them from Stripe when they're in the connected state. Great.