Step-By-Step Guide to Stripe Payments in React

By  on  

This is an adapted from several excerpts from Scott Hasbrouck's book, "The Node.js Engineer's Guide to Stripe" - Available Now! with a 10% discount for David Walsh readers with code: WALSH10

What We’ll Cover

  • Replace Checkout.js with Stripe.js
  • Removing the Checkout.js button
  • Adding required Stripe fields
  • Integration the form action with Stripe.js

When you first build a Stripe integration, the advantage of Checkout.js over Stripe.js is its ease of integration and speed to a working app. However, it does not allow adding any additional input fields. In many situations, you'll want to collect other values such as Quantity, a drop down of products, shipping address, etc, and submit it with the same form that collects payment details. Or perhaps, you really just want a uniform style with the rest of your app that doesn't require a modal dialog to popup. Stripe’s smaller frontend library, called Stripe.js, does not include any UI elements but has all of the client side API functionality of generating payment tokens. Customizing the payment form will require no changes to the backend functionality of your Node.js app, because the front end will still be generating the same payment token.

Brief Overview of Checkout.js Functionality

If you've never integrated Stripe before, or it's been a while since you've done it, let's review just what the purpose is of the front end portion of Stripe! Stripe is an API as a Service, so your first question may be, "Why on earth does an API require the use of a front-end JavaScript library?" Great question! As you can imagine, handling your users' credit card information online is a potentially risky business - which is exactly why there is a security standard that you must adhere to in order to accept payments online. The Payment Card Industry Digital Security Standards (or PCI DSS, commonly just referred to as PCI for short), explicitly prohibits direct storing of credit card numbers by merchants - unless you are up to the task of "protecting stored cardholder data." Stripe's ingenuity was to build a simple front end mechanism that collects the cardholder payment data on your behalf so that it never even touches your server - making PCI-DSS compliance much easier. This is covered in more detail in my book, The Node.js Engineer's Guide to Stripe.

Checkout.js bundles the cardholder data collection mechanism with a beautiful and easy to integrate modal popup form that collects that payment details from the user. This is a fantastic option for putting together a very quick Stripe integration, but will not seamlessly flow with the rest of your user interface. This is where Stripe.js come into play. The API still offers JavaScript methods for sending the payment details directly to Stripe, and receiving a payment token to represent the payment.

Installing Stripe.js

The Stripe documentation lists provides a Script tag that loads Stripe.js with the latest version. It may be tempting to install the Script with Bower by running bower install --save stripe.js=https://js.stripe.com/v2/, but keep in mind doing this is not officially endorsed by Stripe. There is no mention as to how often they update the client side libraries, so something may break on you unexpectedly. So your first option is to simply load the library by placing the Stripe provided script tag in the HTML file that your React app is mounted in:

<html>
    <head>
               <script type="text/javascript" src="https://js.stripe.com/v2/"></script>
    </head>
    <body style="margin: 0px;">
        <div id="main"></div>
        <script src="react-bundle.js"></script>
    </body>
<html>

A much better option would be to dynamically load this script with ReactScriptLoader! Considering a React app is a Single Page App, there are likely huge chunks of your app that do not have a payment form. Why load Stripe.js for the entire page when we can simply load it for just the payment form component? Let's make an empty React component for our payment form and dynamically load Stripe.js (note, this method would work just as well for Checkout.js!):

var React = require('react');
var ReactScriptLoaderMixin = require('react-script-loader').ReactScriptLoaderMixin;

var PaymentForm = React.createClass({
  mixins: [ ReactScriptLoaderMixin ],

  getInitialState: function() {
    return {
      stripeLoading: true,
      stripeLoadingError: false
    };
  },

  getScriptURL: function() {
    return 'https://js.stripe.com/v2/';
  },

  onScriptLoaded: function() {
    if (!PaymentForm.getStripeToken) {

      // Put your publishable key here
      Stripe.setPublishableKey('pk_test_xxxx');
      this.setState({ stripeLoading: false, stripeLoadingError: false });
    }
  },

  onScriptError: function() {
    this.setState({ stripeLoading: false, stripeLoadingError: true });
  },

  render: function() {
    if (this.state.stripeLoading) {
      return <div>Loading</div>;
    }
    else if (this.state.stripeLoadingError) {
      return <div>Error</div>;
    }
    else {
      return <div>Loaded!</div>;
    }
  }
});

module.exports = PaymentForm;

The ReactScriptLoaderMixin begins loading the remote script, and upon successfully loading it, or reaching an error, will invoke one of two event listeners. Once the script is successfully loaded, we can set the public key for Stripe.js. This in turn, gives us a conditional in the render function for three states of loading, errored, or loaded! Note that this method can also be used to load Checkout.js.

Building the Form

Now we have a React component with Stripe.js loaded, let's start building the custom payment form. At minimum, we need to collect four values for Stripe to generate a payment token for us: credit card number, expiration month, expiration year, and the cvc.

var React = require('react');
var ReactScriptLoaderMixin = require('react-script-loader').ReactScriptLoaderMixin;

var PaymentForm = React.createClass({
  mixins: [ ReactScriptLoaderMixin ],

  getInitialState: function() {
    return {
      stripeLoading: true,
      stripeLoadingError: false,
      submitDisabled: false,
      paymentError: null,
      paymentComplete: false,
      token: null
    };
  },

  getScriptURL: function() {
    return 'https://js.stripe.com/v2/';
  },

  onScriptLoaded: function() {
    if (!PaymentForm.getStripeToken) {
      // Put your publishable key here
      Stripe.setPublishableKey('pk_test_xxxx');

      this.setState({ stripeLoading: false, stripeLoadingError: false });
    }
  },

  onScriptError: function() {
    this.setState({ stripeLoading: false, stripeLoadingError: true });
  },

  onSubmit: function(event) {
    var self = this;
    event.preventDefault();
    this.setState({ submitDisabled: true, paymentError: null });
    // send form here
    Stripe.createToken(event.target, function(status, response) {
      if (response.error) {
        self.setState({ paymentError: response.error.message, submitDisabled: false });
      }
      else {
        self.setState({ paymentComplete: true, submitDisabled: false, token: response.id });
        // make request to your server here!
      }
    });
  },

  render: function() {
    if (this.state.stripeLoading) {
      return <div>Loading</div>;
    }
    else if (this.state.stripeLoadingError) {
      return <div>Error</div>;
    }
    else if (this.state.paymentComplete) {
      return <div>Payment Complete!</div>;
    }
    else {
      return (<form onSubmit={this.onSubmit} >
        <span>{ this.state.paymentError }</span><br />
        <input type='text' data-stripe='number' placeholder='credit card number' /><br />
        <input type='text' data-stripe='exp-month' placeholder='expiration month' /><br />
        <input type='text' data-stripe='exp-year' placeholder='expiration year' /><br />
        <input type='text' data-stripe='cvc' placeholder='cvc' /><br />
        <input disabled={this.state.submitDisabled} type='submit' value='Purchase' />
      </form>);
    }
  }
});

module.exports = PaymentForm;

Once Stripe.js is loaded, our payment form component returns a form with the required input fields. We’ve added the required data-stripe attributes per the Stripe documentation. The form onSubmit event invokes a handler on our component which calls Stripe.createToken(). If an error is returned, we display that to our users by setting state.paymentError equal to the error message. Otherwise, we set the payment is complete with this.paymentComplete, and that is also the point where we would pass the token and required purchasing information to our server with a module such as superagent.

Summary

As you can see, nixing Checkout.js for your own custom styled payment form is really not very difficult. By making this a component and loading Stripe.js dynamically, it also keeps the resources that must be loaded by the client to a minimum, and allows you to drop this into any place you need to complete a purchase in your React app. Once you have this boilerplate React component setup for interacting with Stripe.js, you can add other fields related to the product the user is purchasing, or even make collecting credit card information a seamless step of your signup process. Your users will never know that you are relying on Stripe to do this.

Checkout.js does add a layer of perceived security by showing the Stripe brand, and recognizing the card type as you enter your credit card number. I would recommend putting some effort into showing visual clues of security for the user when building your own form, also. For example, this would be a great place to show your SSL certificate badge from Comodo or Network Solutions. To further comfort your users, integrating something similar to react-credit-card would be a great finishing touch. This component automatically detects credit card type, and shows the appropriate logo on a CSS generated credit card, along with the credit card number itself.

Thankfully, integrating Stripe on your front end is fairly straightforward - it does not really get much more complicated than this! The real work (and fun!) begins on your server code, which can become complicated and buggy if you are doing more than accepting one-time payments for non-repeat users. Best of luck on your online payment endeavors with JavaScript, and if you want input on your own projects, or have feedback with how you've integrated Stripe with React please reach out or comment! The first five people to leave a comment about their favorite takeaway from this post or React tip and tweet the article will recieve a FREE copy of my book: The Node.js Engineer's Guide to Stripe! Just mention me in the tweet and I will DM you directions on how to claim your copy.

Scott Hasbrouck

About Scott Hasbrouck

Scott is a lifelong software engineer, that loves to share his skills with others through writing and mentorship. As a serial entrepreneur, he has started 3 companies as a technical founder, bootstrapping one to over one million users. He's always seeking the next adventure through hiking remote places, flying small airplanes, and traveling.

Recent Features

  • By
    JavaScript Promise API

    While synchronous code is easier to follow and debug, async is generally better for performance and flexibility. Why "hold up the show" when you can trigger numerous requests at once and then handle them when each is ready?  Promises are becoming a big part of the JavaScript world...

  • By
    Interview with a Pornhub Web Developer

    Regardless of your stance on pornography, it would be impossible to deny the massive impact the adult website industry has had on pushing the web forward. From pushing the browser's video limits to pushing ads through WebSocket so ad blockers don't detect them, you have...

Incredible Demos

  • By
    Retrieve Google Analytics Visits and PageViews with PHP

    Google Analytics is an outstanding website analytics tool that gives you way more information about your website than you probably need. Better to get more than you want than not enough, right? Anyways I check my website statistics more often than I should and...

  • By
    Simple Image Lazy Load and Fade

    One of the quickest and easiest website performance optimizations is decreasing image loading.  That means a variety of things, including minifying images with tools like ImageOptim and TinyPNG, using data URIs and sprites, and lazy loading images.  It's a bit jarring when you're lazy loading images and they just...

Discussion

  1. PCI is a mandatory part of any payment processing workflow and it should cause sleepless nights…

    The react card library looks awesome.

  2. Wasn’t expecting this to be so succinct. Great article, will definitely be building off of your example code and checking out your book.

  3. Seth

    I noticed you are using mixins, however, as on react 0.13, those are being deprecated. Do you have any suggestions on alternative methods for lazy loading Stripe.js?

  4. Great article Scott!

    The part about lazy-loading Stripe.js was exactly what I was looking for.
    I also used the “react-async-script” lib you suggested to make it work in an ES6 app.

    Thanks a lot!

  5. I found this guide very helpful! ! I want to share with you an article about JavaScript Unit Test Automation on React Components which reveals a completely new method that doesn’t rely on React’s test utilities, I’m sure you’ll find it helpful: http://blog.testproject.io/2016/09/21/javascript-unit-test-automation-react-components/

  6. Can you point me to React’s Report on Compliance (ROP) so I can safely use their library in my PCI environment?

  7. Andrew Coppock

    Thanks for this write up. I was just asked to assist with writing an app for our church to accept giving using their stripe account. I hope to use your guide to assist in that effort.

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!