Clue Mediator

Integrate stripe payment gateway in React

šŸ“…December 18, 2020
šŸ—ReactJS

Today weā€™ll explain to you how to integrate stripe payment gateway in React. We will split this integration in the two parts such as React (Frontend) and Node.js (Backend).

In this article, will create a react application that contains the product cart and checkout form as shown below.

Demo Application

Output - Integrate stripe payment gateway in React - Clue Mediator

Output - Integrate stripe payment gateway in React - Clue Mediator

Stripe payment gateway integration.

Steps to integrate stripe payment gateway in React

  1. Create a stripe account
  2. Collect the API keys
  3. Create a react application
  4. Install npm dependency
  5. Design a checkout page
  6. Create a checkout form using stripe package
  7. Submit payment method to server
  8. Output

1. Create a stripe account

First of all, we have to create a stripe account using the Register Now link. It's a very straightforward process using the email address.

2. Collect the API keys

After successful login, you will be redirected on the Dashboard page and the API keys are always available on the dashboard. For the testing purpose, weā€™ll use the test API keys. Check the following links for more information about the API keys.

Manage your API keys to authenticate requests with Stripe

There are two types of API keys.

  • Publishable API key: Itā€™s used in places like your Stripe.js, JavaScript code, or in an Android or iPhone app. Weā€™ll use it in the React application.
  • Secret API key: It should be kept confidential and only stored on your own servers. Weā€™ll use it in the Node.js application.

3. Create a react application

Letā€™s create a react application using the `create-react-app` package. For that simply run the following command to Create a React App.

npx create-react-app stripe-payment-gateway-react

4. Install npm dependency

We will use the @stripe/react-stripe-js and @stripe/stripe-js npm packages to integrate the stripe payment gateway in the react app. Run the following single command to install both packages simultaneously.

npm install --save @stripe/react-stripe-js @stripe/stripe-js

5. Design a checkout page

Now, we will design a checkout page using bootstrap with some sample records. Add the following bootstrap stylesheet to the `public.html` page.

<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">

You can also check How to add Bootstrap in React.

Letā€™s add the following HTML and CSS of the checkout page to the react component.

App.js

import React, { useState } from 'react';

const successMessage = () => {
  return (
    <div class="success-msg">
      <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-check2" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
        <path fill-rule="evenodd" d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"></path>
      </svg>
      <div class="title">Payment Successful</div>
    </div>
  )
}

const cart = () => {
  return (<react class="fragment">
    <h4 class="d-flex justify-content-between align-items-center mb-3">
      <span class="text-muted">Your cart</span>
      <span class="badge bg-secondary badge-pill">3</span>
    </h4>
    <ul class="list-group mb-3">
      <li class="list-group-item d-flex justify-content-between lh-condensed">
        <div>
          <h6 class="my-0">Product name</h6>
          <small class="text-muted">Brief description</small>
        </div>
        <span class="text-muted">ā‚¹1200</span>
      </li>
      <li class="list-group-item d-flex justify-content-between lh-condensed">
        <div>
          <h6 class="my-0">Second product</h6>
          <small class="text-muted">Brief description</small>
        </div>
        <span class="text-muted">ā‚¹800</span>
      </li>
      <li class="list-group-item d-flex justify-content-between lh-condensed">
        <div>
          <h6 class="my-0">Third item</h6>
          <small class="text-muted">Brief description</small>
        </div>
        <span class="text-muted">ā‚¹500</span>
      </li>
      <li class="list-group-item d-flex justify-content-between bg-light">
        <div class="text-success">
          <h6 class="my-0">Promo code</h6>
          <small>EXAMPLECODE</small>
        </div>
        <span class="text-success">-ā‚¹500</span>
      </li>
      <li class="list-group-item d-flex justify-content-between">
        <span>Total (INR)</span>
        <strong>ā‚¹2000</strong>
      </li>
    </ul>
  </react>)
}

function App() {
  const [paymentCompleted, setPaymentCompleted] = useState(false);

  return (
    <div class="container">
      <div class="py-5 text-center">
        <h4>Stripe Integration - <a href="https://www.cluemediator.com/" target="_blank" rel="noopener noreferrer">Clue Mediator</a></h4>
      </div>

      <div class="row s-box">
        {paymentCompleted ? successMessage() : <react class="fragment">
          <div class="col-md-5 order-md-2 mb-4">
            {cart()}
          </div>
          <div class="col-md-7 order-md-1">
            {/* Checkout form */}
          </div>
        </react>}
      </div>

    </div>
  );
}

export default App;

index.css

body {
  margin: 20px;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

.container {
  width: 900px;
}
.s-box {
  min-height: 433px;
  border: 1px solid #ddd;
  border-radius: 10px;
  padding: 30px;
  background-color: #f7f7f9;
  box-shadow: 0 1px 1px rgba(0,0,0,0.15),
  0 2px 2px rgba(0,0,0,0.15),
  0 4px 4px rgba(0,0,0,0.15),
  0 8px 8px rgba(0,0,0,0.15);
}
label {
  margin-bottom: 3px;
}
.spinner-border {
  width: 1.3rem;
  height: 1.3rem;
  border-width: .1em;
}

.success-msg {
  color: #0f5132;
  text-align: center;
  margin-top: 120px;
}
.success-msg svg {
  font-size: 60px;
  border: 1px solid #0f5132;
  border-radius: 30px;
  padding: 10px;
}
.success-msg .title {
  font-size: 25px;
  margin-top: 10px;
}

6. Create a checkout form using stripe package

Itā€™s time to create a checkout form using the stripe package. Here we will use the individual `Element` component to create a form. You can also use the `CardElement` to create a ready form.

CheckoutForm.js

import React, { useState } from 'react';
import {
  useStripe, useElements,
  CardNumberElement, CardExpiryElement, CardCvcElement
} from '@stripe/react-stripe-js';

const CARD_ELEMENT_OPTIONS = {
  style: {
    base: {
      lineHeight: "27px",
      color: "#212529",
      fontSize: "1.1rem",
      "::placeholder": {
        color: "#aab7c4",
      },
    },
    invalid: {
      color: "#fa755a",
      iconColor: "#fa755a",
    },
  },
};

export default function CheckoutForm(props) {
  const [loading, setLoading] = useState(false);
  const [errorMsg, setErrorMsg] = useState('');

  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const stripe = useStripe();
  const elements = useElements();

  const handleSubmit = async (event) => {
    // We don't want to let default form submission happen here,
    // which would refresh the page.
    event.preventDefault();

    if (!stripe || !elements) {
      // Stripe.js has not yet loaded.
      // Make sure to disable form submission until Stripe.js has loaded.
      return;
    }
  };

  return (
    <react class="fragment">
      <h4 class="d-flex justify-content-between align-items-center mb-3">
        <span class="text-muted">Pay with card</span>
      </h4>
      <form onsubmit={handleSubmit}>

        <div class="row">
          <div class="col-md-6 mb-3">
            <label for="cc-name">Name on card</label>
            <input id="cc-name" type="text" class="form-control" value={name} onchange="{e" ==""> setName(e.target.value)}
            />
          </div>
          <div class="col-md-6 mb-3">
            <label for="cc-email">Email</label>
            <input id="cc-email" type="text" class="form-control" value={email} onchange="{e" ==""> setEmail(e.target.value)}
            />
          </div>
        </div>

        <div class="row">
          <div class="col-md-12 mb-3">
            <label for="cc-number">Card Number</label>
            <cardnumberelement id="cc-number" class="form-control" options={CARD_ELEMENT_OPTIONS}>
          </cardnumberelement></div>
        </div>

        <div class="row">
          <div class="col-md-6 mb-3">
            <label for="expiry">Expiration Date</label>
            <cardexpiryelement id="expiry" class="form-control" options={CARD_ELEMENT_OPTIONS}>
          </cardexpiryelement></div>
          <div class="col-md-6 mb-3">
            <label for="cvc">CVC</label>
            <cardcvcelement id="cvc" class="form-control" options={CARD_ELEMENT_OPTIONS}>
          </cardcvcelement></div>
        </div>

        <hr class="mb-4">
        <button class="btn btn-dark w-100" type="submit" disabled>
          {loading ? <div class="spinner-border spinner-border-sm text-light" role="status"></div> : `PAY ā‚¹${props.amount}`}
        </button>
        {errorMsg && <div class="text-danger mt-2">{errorMsg}</div>}
      </form>
    </react>
  );
}

To load the `CheckoutForm` component, we need to generate the `Stripe` object using the publishable API key. Check the following code.

App.js

import React, { useState } from 'react';
import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
import CheckoutForm from './CheckoutForm';

// Make sure to call `loadStripe` outside of a componentā€™s render to avoid
// recreating the `Stripe` object on every render.
const stripePromise = loadStripe("<your_publishable_key>");

const successMessage = () => {
  return (
    <div class="success-msg">
      ...
      ...
    </div>
  )
}

const cart = () => {
  return (
    <react class="fragment">
      ...
      ...
    </react>
  )
}

function App() {
  const [paymentCompleted, setPaymentCompleted] = useState(false);

  return (
    <div class="container">
      <div class="py-5 text-center">
        <h4>Stripe Integration - <a href="https://www.cluemediator.com/" target="_blank" rel="noopener noreferrer">Clue Mediator</a></h4>
      </div>

      <div class="row s-box">
        {paymentCompleted ? successMessage() : <react class="fragment">
          <div class="col-md-5 order-md-2 mb-4">
            {cart()}
          </div>
          <div class="col-md-7 order-md-1">
            <elements stripe={stripePromise}>
              <checkoutform amount={2000} setpaymentcompleted={setPaymentCompleted}>
            </checkoutform></elements>
          </div>
        </react>}
      </div>

    </div>
  );
}

export default App;
</your_publishable_key>

7. Submit payment method to server

In the last step, we need to create a payment method on button click of the `Pay`. After creating the payment method, we have to send that data to the backend like payment method id, name, email, amount, etc.

So letā€™s create a `stripePaymentMethodHandler` method to submit that data to the backend.

script.js

const API_ENDPOINT = 'http://localhost:4000';

export const stripePaymentMethodHandler = async (data, cb) => {
  const { amount, result } = data;
  if (result.error) {
    // show error in payment form
    cb(result);
  } else {
    const paymentResponse = await stripePayment({
      payment_method_id: result.paymentMethod.id,
      name: result.paymentMethod.billing_details.name,
      email: result.paymentMethod.billing_details.email,
      amount: amount
    });
    console.log(paymentResponse);
    cb(paymentResponse);
  }
}

// place backend API call for payment
const stripePayment = async data => {
  const res = await fetch(`${API_ENDPOINT}/pay`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  })
  return await res.json();
}

Here, we have used the `API_ENDPOINT` as a backend server API endpoint where we will confirm the payment. Weā€™ll see this step in the next article (Confirm a stripe paymentIntent using Node.js).

Letā€™s call the above method from the `CheckoutForm` component.

CheckoutForm.js

import { stripePaymentMethodHandler } from './script';

...
...

export default function CheckoutForm(props) {

  ...
  ...

  const handleSubmit = async (event) => {
    // We don't want to let default form submission happen here,
    // which would refresh the page.
    event.preventDefault();

    if (!stripe || !elements) {
      // Stripe.js has not yet loaded.
      // Make sure to disable form submission until Stripe.js has loaded.
      return;
    }

    setLoading(true);
    setErrorMsg('');

    const paymentMethodObj = {
      type: 'card',
      card: elements.getElement(CardNumberElement),
      billing_details: {
        name,
        email
      },
    };
    const paymentMethodResult = await stripe.createPaymentMethod(paymentMethodObj);

    stripePaymentMethodHandler({
      result: paymentMethodResult,
      amount: props.amount
    }, handleResponse);
  };

  // callback method to handle the response
  const handleResponse = response => {
    setLoading(false);
    if (response.error) {
      setErrorMsg(typeof response.error === 'string' ? response.error : response.error.message);
      return;
    }
    props.setPaymentCompleted(response.success ? true : false);
  };

  return (
    <react class="fragment">
      ...
      ...
    </react>
  );
}

Check this link for the more information of the stripe integration.

8. Output

Run the react application and check the output in the browser.

Note: Here, we have used the backend server API to confirm the payment, which we explained in the next article.

Thatā€™s it for today.
Thank you for reading. Happy Coding..!!

Demo & Source Code

Github Repository StackBlitz Project