Blog

Create a simple payment flow with Stripe

Learn how to easily set up a payment flow on your web application using a ReactJs frontend, a NestJs backend, and Stripe’s payment Intents!

The Stripe API allows you to create a payment flow. They propose different ways to achieve your goal, each with their ups and downs.

The one we will use is the payment Intent for two reasons: First, the credit card payment is really easy to set-up. Second, because of the new law for payment security, you now have to monitor the entire process of checking out and the payment Intents is perfect for this purpose!

Let’s start easy: How to create your payment form? We will use React on our frontend with the react-stripe-elements package to construct our checkout form:

There are several components provided by React Stripe Elements:

<StripeProvider> creates a stripe instance for your frontend app, with it you can contact the Stripe API to create Intents and carry out payments. It takes one stripe prop that contains an object with your unique API key and a key indicating that you are using payment Intents and not another Stripe APIs.

Your API key is provided in your dashboard after creating a stripe account. Make sure to use the test key (‘pk_test_…’) and not the live one used for production !

<Elements> will contain all your checkout form components, wether your have one (like here) or multiple fragmented pieces, they should always be inside an Elements component.

Finally, we use a <CardElement> to create a form containing the card, CVC, zip-code and date of expiry inputs. Of course no one is stopping you from creating your own custom form or use other elements from React Stripe Elements.

Note: Don’t forget to add <script src=”https://js.stripe.com/v3/" /> to your public/index.html

Here’s the result:

Image for post

For now, we just created a static form and created a stripe instance. We now have to actually get a payment Intent from our server and handle the payment. We will extract the payment logic to a different component so we don’t pollute our App component with too specific code:

Here’s whats inside <CheckoutForm>:

First, we have to inject the Stripe instance we created into our component. For that, we use the higher-order component injectStripe() that will return CheckoutForm with the stripe instance.

Then, create a click handler function for the <button> and a ready handler function for the <CardElement>.

In handleReady(), we just save the reference to the checkout form in the state, we will see why later.

In submit(), we have to do a payment with the handleCardPayment() method from the stripe prop we recovered with injectStripe(). To do so we need two parameters: the client secret and the checkout form reference (the one we just saved in state !).

To get a client secret, we have to create a payment Intent. The client secret is a unique key that we will use to create a charge.

Here we leave it’s creation to the server so it may store all data pertaining to the payment flow. In the end, we can just fetch the client secret from our server with an Axios.post() to which we will give the amount to pay and the payment method.

Now that we have all the information required, we can handle the payment really easily with handleCardPayment(). By giving it the CardElement component’s reference — with all the client’s information and the client secret from the payment Intent, Stripe can create a payment at the right bank account.

If you followed the process correctly, you will have noticed that the server doesn’t receive any critical information from the client, as the actual payment to the bank is done by a third-party: Stripe. Your server will only create a payment Intent, so you can have a secure payment flow.

Now on to the fun part with our server! We will create two endpoints: one for creating a payment Intent and the other to listen for webhook events from Stripe.

Here’s how the payment flow works:

Image for post
Image for post

First we need to connect our backend to the Stripe API to recover a Stripe instance. To keep the same unique instance for the entire backend, we will create a custom provider. Create a stripe token in your constants, set up your custom provider and add your Stripe API key to your .env file.

For the controller part: payment.controller.js will contain our two endpoints. The routing prefix of the controller will be ‘payments’.

To create the Intent, we create a Post() route under the name create with ‘/intent’ as a prefix. Then call a create service that will handle the Intent creation.

Create the DTO for the create route:

In your payment.service.ts, first inject the Stripe instance from your custom provider, then create the Intent with the paymentIntents.create() method. This can take multiple parameters, see the documentation for more info. We will give it the amount, the card type and the currency for now.

You can then call a DAL service to write the payment Intent in your database and return the result to your controller.

Now, in the frontend, the client can receive the client secret and issue a payment. With payment Intents, you can monitor the entire process with Stripe’s Webhooks:

In your payment controller, create a second Post() route called ‘validation’ with the prefix ‘/validate-payment’. This will receive events from Stripe whenever you do one of the following: create an intent, update it, create a payment and/or the status (success or error) of that payment.

We need the raw body of the request — we will see why later. If your body parser is set to JSON for example, you can recover the raw value by adding a ‘rawBody’ key on the request with the body-parser middleware:

Create the DTO for the validation, then add a paymentValidation service in payment.service.ts:

Here we check the type of event received, if the charge is a success, with a DAL service we change the status of the payment in the database from ‘pending’ to ‘success’, however if we receive a payment failure event, we change the status to ‘error’.

This is all good, but how can we verify the events received on our endpoint? Can we really trust that the request comes from Stripe?

No worries, here is how you can check the integrity of the request and the identity of the sender:

The constructEvent() method checks the validity of the signature from the body of the request against your Webhook secret, if the body was tempered with or fabricated by a third-party, an error will be thrown. You can get your Webhook secret from your Stripe Dashboard.

Now that the app is setup, go into your Stripe Dashboard and create the Webhook endpoint with the name of your validation route: https://your-website.com/payments/validate-payment. You can choose the events you want to receive or take them all.

Note that you need to respond to all Webhook events with a status code or a {received: true} so that Stripe is made aware that the event was received. The controller automatically sends a status code 200.

You have learned a quick and easy way to set-up credit card payments for a React.Js frontend and Nest.Js Backend with Stripe’s payment Intents. Hope this quick tutorial was helpful, and do not hesitate to ask your questions in the comments below!


Read more from this Author
No items found.