to navigateEnterto select Escto close

    Accepting payments using stripe

    Accepting payments using stripe

    Stripe allows two ways to accept payment online. Either we can send the customer to some other page where customer can make the payment or the application can host the page itself.

    Stripe Checkout allows us to send customers to a stripe hosted page.

    Stripe Elements provides us the necessary tools to host the checkout page in the application itself.

    Both the solutions have their own advantage and disadvantages. Let's look at these two solutions more closely.

    Stripe Checkouts

    Stripe Checkout allows us to send our customers to a Stripe hosted checkout page.


    • Works out of the box on mobile devices with a responsive UI.
    • Works out of the box on all platforms since Stripe has tested it on various platforms.
    • Accepts different kinds of cards, with localized support for further actions during payment like SCA, 3D-secure etc based on location.
    • Supports google pay, apple pay etc and it's presence can be controlled using stripe checkout API itself.
    • Allows basic level customization and branding on the page.
    • Real time card validation and built in error messaging system.
    • Allows applying discounts, collecting taxes, address auto-completions etc.
    • The brand name of Stripe give peace of mind to the folks who already know about Stripe.
    • Since this page is hosted by Stripe it is considered very secure.

    Things to watch out for

    The Stripe hosted checkout page allows customers to change their email. This could be a problem in some cases. Let's take an example.

    Say our app determines customer's access to our product based on whether the payment has been made or not. Customer do not need to register or create an account to make the payment. Customer provides email address and credit card information and a payment is made.

    Currently the customer has access to our product. Later the customer cancels the subscription, thus disallowing access to the product, since only actively subscribed customers will have access to product. Thus we can use this email to uniquely identify a customer.

    So what happens the next time the customer tries to re-subscribe? If the customer provides the same email in the Stripe checkout page, then a new customer will be created, with that same email, in Stripe.

    This action will store two customers at Stripe's end, where one is already in cancelled state and the other one in active state, and both will have the same email. This is the way Stripe handles customer creation in checkout page.

    But this can cause a lot of confusion given that email is often considered as an unique entity, which is used to uniquely identify a customer, in most backend operations.

    This shows that we have to implement a strategy, in order to avoid creating multiple customers with the same email at Stripe's end. Let's try to devise a strategy which can overcome this hurdle in the least complicated manner.

    As the first line of defense, we need to figure out whether the customer is subscribing for the first time or not. Why? Because if the customer is re-subscribing, then there would already be an existing customer object at Stripe's end. If it's first time, there won't be any such customer at Stripe's end.

    If the customer is subscribing for the first time, then checkout session can be created by providing the email that the customer had provided. We will look into how to implement a checkout session in this manner in the upcoming section.

    This will ensure that Stripe automatically pre-populates the email field in the Stripe hosted checkout page and disables it, such that the customer can't change the email from the hosted checkout page.

    If the customer is re-subscribing then we don't need to create a new customer at Stripe's end. Rather we can tell stripe that the current customer is same as the one already in Stripe, since their emails are same.

    We can let Stripe know that the current customer is same as the one already existing at Stripe's end, when creating the checkout session itself. We will look into the implementation details in the upcoming section regarding this case.

    In the latter case, in the checkout page, the email of the customer will be pre-filled, but still can be edited by the customer.

    But no worries, even if the customer updates their email, we need to update the same info in our backend. This can handled by listening to customer update events from Stripe.

    Another caveat is that the amount of customization that we can do on the Stripe hosted checkout page is limited to few buttons and colors. So branding is also limited.

    Steps to create a checkout session

    • At client side we need to install the npm package stripe-js, and in backend, depending on our stack, the corresponding official library needs to be setup.

      1# client side
      2yarn add @stripe/stripe-js
      3# server side for Rails, add the following into Gemfile and do bundle install
      4gem "stripe"
      5# server side for Node.js
      6yarn add stripe
    • Next step is the initialization. During the initialization step we need to provide the api key and secret to the stripe object, which can be exported and used for all stripe related operations. For Rails in initializers/stripe.rb, add the following content:

      1# Here we are using the Figaro gem to handle env variables
      2Rails.configuration.stripe = {
      3  publishable_key: Figaro.env.stripe_secret_key,
      4  secret_key: Figaro.env.stripe_publishable_key
      7Stripe.api_key = Figaro.env.stripe_secret_key
      8Stripe.api_version = '2020-08-27'

      For Node.js, we can use the following initializer in each of the components:

      1import Stripe from "stripe";
      3const stripe = new Stripe(process.env.stripe_secret_key, {
      4  apiVersion: "2020-08-27",
    • Create the products and their corresponding prices from Stripe dashboard or using stripe-cli. We will have to store the Stripe allotted ID's for Prices in our server for further interactions, like say subscribing to a particular product with a particular price.

    • Optional step: Configure the customer portal. The customer portal allows the user to cancel plan, update credit card info, update subscription quantities etc. These features can be directly implemented in the application itself without much hassle or can be delegated to Stripe's own customer portal. Since this abstracts away a lot of the Stripe functionalities from the developer, we won't be implementing this feature.

    • Create a checkout session. Here basically we need to create a checkout session ID in our backend and send it to our client and redirect to the Stripe hosted checkout page using that ID. The ID will help Stripe understand things like, to which url Stripe must redirect once checkout is success or not, is the payment a subscription or one-time payment, which item is being checked out etc. Let's see an example of how it's done in Rails backend:

      1checkout_options = {
      2  success_url: "#{Figaro.env.webapp_url}/checkout/success?session_id={CHECKOUT_SESSION_ID}",
      3  cancel_url: "#{Figaro.env.webapp_url}/",
      4  payment_method_types: ['card'],
      5  mode: 'subscription',
      6  line_items: [{
      7    quantity: 1,
      8    price: price_id_from_label
      9  }]
      12session = Stripe::Checkout::Session.create(checkout_options)
      14render status: :ok, json: { sessionId: }
    • Optional step: If we want to avoid customer duplication at Stripe's end, when the customer re-subscribes using Stripe hosted checkout form, then we need to conditionally add the already existing customer's Stripe ID to the checkout options. If no customer already exists, then we can pass in the email. We can add a logic similar to one shown below before creating the checkout session:

      1checkout_options.merge!(if existing_customer.present?
      2                          { customer: existing_customer.stripe_customer_id }
      3                        else
      4                          { customer_email: checkout_params['email'] }
      5                        end)
    • Redirect customers to Stripe hosted checkout form using the checkout session ID received from backend. Which would look something like this:

      1const { error: stripeError } = stripe.redirectToCheckout({
      2  sessionId: checkoutAPIData?.sessionId,
    • Monitor and provision the checkout, and provide access to the application. Think asynchronous! Stripe checkouts or more accurately card payments, are often not synchronous. Therefore we need to leverage Stripe webhook events to make sure that the customer has indeed made the payment and provision further access to our products/applications based on the event response.

    Stripe Elements

    Stripe Elements are prebuilt UI components provided by Stripe for the developers to integrate into their own UI. These components allow the developers to have more control over each and every step, rather than delegating all control to Stripe hosted UI's. Let's take a look into what makes Stripe Elements a popular choice among some developers.


    • Provides real time validation as customer types, including fields like card details and address.
    • Allows maximum customization and control over look and feel of the payment form.
    • Allows localization features like dynamically detecting which inputs are needed based card's country and location.
    • Easy to plug and play into our existing UI codebase.
    • Most important point is that it provides a great level of control for the developers over how each Stripe action, like say Checkout, is handled.

    Things to watch out for

    • It's sometimes not straightforward to manage the stylesheets and update it dynamically, specifically for the CardElements and all.
    • The reason is that these card elements etc, for being secure, are displayed in a html iframe, which won't allow any styles to trickle down from parent to it. It has its own style context, called the options object, which can passed to card elements.
    • For taking further steps which requires more action from customers side like say accessing 3D-secure payment in SCA compliant countries during checkout etc, we need to handle the logic in our own website itself.
    • The overall UI/UX of the page needs to be taken care of from our side, so that the page remains responsive across all devices.
    • All cases of errors needs to be handled and notified to the user from our side itself.

    Steps to make a payment using stripe elements

    • Setup the stripe-js library and react-stripe-js library for making use of the UI components.

      1yarn add @stripe/react-stripe-js @stripe/stripe-js
    • Create the stripe object, which is reference to all of Stripe's front-end SDK's. Make use of the loadStripe helper from stripe-js, passing in the publishable key, to return a Promise which will resolve to this object.

    • Wrap the child components where Stripe object reference and card payment is needed, within the Elements provider from react-stripe-js, where the stripe prop will be set to Promise from above step.

    • The Elements provider allows us to use Card components and access the Stripe object in any nested component. Usually the provider is rendered at the root of the App, in order to make it available everywhere in the app.

      1import { Elements } from "@stripe/react-stripe-js";
      2import { loadStripe } from "@stripe/stripe-js";
      4// Make sure to call `loadStripe` outside of a component’s render to avoid
      5// recreating the `Stripe` object on every render.
      6const stripePromise = loadStripe(
      7  "pk_test_51JN8RDS4ArgcNIller....z50iOJ613G2000VINY9qmK"
      10const App = () => {
      11  return (
      12    <Elements stripe={stripePromise}>
      13      <MyCheckoutForm />
      14    </Elements>
      15  );
    • Next step is to create a CheckoutForm component where we get reference of both the stripe object as well as elements using the useStripe and useElements hooks.

    • In the checkout form, we need to create a stripe paymentMethod, by passing in the reference to mounted CardElement and our billing details.

    • A PaymentMethod contains reusable payment method details for creating payments, e.g: card expiration date or billing address. It does not include transaction-specific information, e.g: amount, currency.

      1import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
      3const CheckoutForm = () => {
      4  const stripe = useStripe();
      5  const elements = useElements();
      7  // Get a reference to a mounted CardElement. Elements knows how
      8  // to find your CardElement because there can only ever be one of
      9  // each type of element.
      10  const cardElement = elements.getElement(CardElement);
      12  // Use your card Element with other Stripe.js APIs
      13  const { error, paymentMethod } = await stripe.createPaymentMethod({
      14    type: 'card',
      15    card: cardElement,
      16  });
      18  return (
      19    <form onSubmit={handleSubmit}>
      20      <CardElement />
      21    </form>
      22  );
    • Once paymentMethod object is successfully created, the next step is very much dependent on the business logic of our application. Here we need to create a new customer and attach a subscription for the price chosen. This should be done in the backend. We need to pass in the collected email for storing to our own DB, ID of payment method, and the price chosen.

    • From the backend we will receive the ID of the subscription, client_secret, which is the unique key for individual paymentIntent, and status of the subscription.

    • If the subscription status is requires_action, then the next step is to confirm the card payment by making using of the client_secret.

      1const cardPaymentStatus = await stripe.confirmCardPayment(client_secret);
    • This confirmation is to make sure that we take care of steps like 3D-secure for SCA compliance. If the subscription status was already paid, then we can provision access to product directly. For all other status, we should disallow user access to product and wait for webhook events for confirmation.

    Stripe Checkout vs Stripe Elements

    Let's talk about an example Stripe action, which is collecting payment. Ultimately from a functionality point of wise, both Stripe Checkout and Elements achieve the same goal.

    Stripe Elements provides more control over UI and allows to keep the customer within the application for doing all sorts of stripe related activities rather than redirecting to another page.

    Stripe Checkout functions a bit differently because it appears on another page. But ultimately they all do the same thing: Take money from the customer and put it in your account and perform other stripe related side effects. The following factors needs to be considered when using either of them.


    • Site's design: Take a moment to evaluate what kind of form best suites your websites current design. Will it be overcrowded if we add a checkout form within the website? How much time are you going to spend making the checkout form usable to the customer and responsive?
    • Type of customers: In order to get maximum conversion rate, we have to think from the customers perspective. Will having Stripe logo and clean UI like the Stripe hosted Checkout page increase the conversion rate with the current customer? Will redirecting away from current application to Stripe hosted Checkout page wary the customer?
    • The best way to figure this out is to test them on some customers/pool of developers and see which one is more effective. But since we don't have that option most of the time, the best choice is to make use of the Stripe hosted checkout page.

    Notable differences between the two

    • Stripe Elements overcomes one major caveat of Stripe Checkout, which is handling the email field. In Stripe hosted checkout page the email can be edited at times.
    • Using Stripe elements we have full control over our fields.
    • Also stripe elements help with custom branding and other UI related enhancements.
    • Stripe hosted Checkout page saves a lot of development time and reduces the chances of failure due to some coding mistake from developers side. The Stripe hosted checkout page is a work of art and a lot of effort was put into creating such a responsive and beautiful page. Thus we can use it without getting tensed about UI/UX.
    • Stripe checkout page is very secure and gracefully handles all error scenarios. Also it provides a lot of goodies like applying discounts, tax calculation, address fields with auto-completion, payments with google/apple pay etc out of the box.