In this article, we will build together a single-page web application that allows the user to securely execute payments online.
We will offer a top-down explanation of the entire Stripe integration process, before deep-diving into the implementation details of the payment solution discussed. Our demo application will be written in React Typescript and Python Flask. Don’t worry if you are not familiar with the programming stack, the application will only contain basic features and functionalities, easily transposable to any other language.
Online payments, and FinTech in general, have become a crucial part of our lives. Recall the last time you had to execute a payment on a web or mobile application. Fueled by the development of e-commerce, chances are that you’re using e-payments on a weekly basis. The tendency to switch to online payments is not new, but the fast move to digitalization that we are witnessing is making it more important than ever. To follow this trend, a large number of developers are now trying to develop a basic knowledge of building robust and secure online payment systems.
In the past, building an online payment solution was way more complicated than it is today. Before Stripe, there were many payment services providers like Worldpay and Sagepay (UK) that offered an all-combined package — for a price. For online-only, Paypal can arguably be a great payments solution as well, especially that its integration is very simple. However, Stripe’s success lies in how they’ve made it a lot easier to integrate. None of the other established parties really offer what contemporary web entrepreneurs would recognize as a proper API and they certainly speak the language of the tech community.
Fortunately, Stripe has made online payment solutions way easier to build and manage. Stripe is a FinTech launched in 2011 to rebuild the economic infrastructure of the internet. Stripe offers a lot of services for developers to manage payments, transfers, refunds, and fees both on the front-end and the back-end side. Developers no longer need to worry about the infrastructure needed to execute payments, Stripe has developed a complete layer that takes care of all the hassle for you.
Some other tools are available in the market like Square and Paypal. Square comes with a fully integrated POS system with an online store and a payment solution, it is better suited for brick and mortar businesses. A point of sale system, or POS, is the place where your customer makes a payment for products or services at your store. Simply put, every time a customer makes a purchase, they’re completing a POS transaction.
Paypal offers a variety of options for accepting online payments, it is also known for its ease of use. It is available in many more countries than Stripe. However, Stripe remains more customizable.
One additional criterion that you want to consider, besides worldwide adoption and ease of integration, is the fees structure. Although Paypal is more attractive for micropayments (payments under 10$), Stripe certainly has the edge for higher-value payments, especially international payments.
Unless you’re processing US-based microtransactions on the regular, the answer is blissfully conclusive in this case: Stripe.
Stripe offers developers large libraries to take care of the front-end and back-end sides of the payment execution. Amongst other capabilities, the libraries contain objects that allow you to:
Stripe libraries also offer UI components that you can import directly in your front-end. As you will see in the demo application, we will be using a Stripe-ready input field to manage credit card number inputs.
To use Stripe in your project, you will need to create a Stripe account. You can easily do that by visiting the Stripe dashboard login page and clicking on sign up. Once you have completed the setup of your account, you will be able to access your Stripe dashboard.
The Stripe dashboard allows you to easily check all the transactions happening on your account and all the details that you need to successfully integrate payments into your application. The developers' tab in the side menu is extremely important to check logs during testing, to retrieve your secret keys to log in successfully from your application, and to configure webhooks to get notified of Stripe events in your application.
During testing, make sure that you turn on ‘View test data’ in the side menu. It allows you to switch to Stripe’s full testing environment where you can test all features without having to execute real transactions or upload official documents.
Note that API keys are different between the testing and production environment. You can account for this in your application by separating development and testing configuration from production configuration, which we will not demonstrate in our demo app, for the sake of simplicity.
Payments are delicate to manage since we’re dealing with very important user data: their credit card numbers. Credit card processing is completely handled by Stripe. Thus, the security and compliance requirements on our application are not as heavy. Payments transit directly via the frontend towards Stripe APIs. However, we would want to keep track of the entire history of payments that were executed via our platform. Therefore, there should be a mechanism that allows us simultaneously store payment information in our database without communicating sensitive data to the backend.
Fortunately, Stripe has a system of ‘Payment Intents’ that allows us to achieve this.
Step 1: The client application (or frontend) sends a request to the backend to signal that the user is willing to pay a certain amount for a specific order. The request contains all the information necessary about the order like prices and article details for example.
Step 2: The backend calls Stripe’s API to create a ‘Payment Intent’. The intent is created by Stripe and sent back to the backend.
Step 3: The backend returns a client_secret
contained inside the Payment Intent object to the frontend. This code will allow Stripe to recognize that the payment made with this client corresponds to the payment intent created by the corresponding backend.
Step 4: The client executes the payment using the Stripe frontend development tools. The client_secret
is sent with the payment request to Stripe to complete the requirements needed.
Step 5: A status is sent back from Stripe to the client.
After explaining the theory behind executing payments in Stripe, we want to deep dive into a practical example of implementing a payment form in your web application.
You can check the source code on Github.
We will begin by deep-diving into the API source code. As we have previously seen, the API's goal is to create a payment_intent
with the amount that the user is willing to pay, and any additional information that describes the payment like the purchase type and the purchase details. Once this payment_intent
is created, the API will return a client_secret
to the client.
The stripe package should be installed in your Flask project to import it. Moreover, you will need to add your stripe API key that you can have in the Developers menu in your Stripe dashboard once you create your account.
The most important thing that we need to verify in the request sent from the client is that it contains an amount
field that specifies the amount of the payment. You might add other information verification depending on your use case.We create the payment_intent
in line 19 and return the client_secret
in line 22.
It is worthy to note that in this example, for demo purposes, we’re sending the error to the user as a response to the request. This is in general a bad practice! We should always personalize the errors we send to the client to avoid any leakage of sensitive information in the exception messages like passwords, secret keys, and library versions that might be vulnerable.
On the client side, we build a CheckoutForm
which will contain the fields to input the credit cardholder's name, address, and card details. To connect this CheckoutForm
to the stripe account of the application, we need to wrap the form with a <Elements>
tag containing a stripePromise
generated with your specific publishable key
that you can get from the API keys section on your stripe dashboard. This enables the stripe elements that we are going to use in our application to communicate very easily to the stripe servers.
We will take a quick look at the CheckoutForm
component where all the logic behind the payment happens.
First, you can see that when the component first renders, a call is sent to the create-payment-intent
root with the payment details in the request’s body as we have seen in the server-side implementation. The client secret received will populate a state variable and will be used later on to execute the payment.
The form contains 3 Field
components to update the billing information stored in a state variable.
The CardElement
component is directly connected to Stripe, thanks to the Elements
wrapper previously discussed. On every change in this field, the change event is sent to the Stripe API and a response is received and processed in the handleChange
function. So the user will know in real-time if the details of the card are acceptable.
The handleSubmit
function checks that all Stripe elements are correctly loaded before executing any payment. The payment is executed by calling stripe.confirmCardPayment
with the client secret and the card element containing the card information.
If the payment is successful, the error is set to null
and a link to the stripe dashboard is shown to the developer where he can go and verify the payment’s execution. For customers, this should be obviously changed to an adapted success message.
As you have seen in the example above, Stripe has made the integration of payment forms very simple in any web application. However, this is a small demo of all the capabilities that Stripe can provide. The next step is to dig deeper into handling payment events like payment success and refunds in the backend via webhooks.