Stripe card payment setup with MERN stack app



Create a project folder as for example stripe-card-payment and then create a folder 
named backend inside the project directory and install some NPM packages with the backend directory like...

npm install express stripe dotenv


Create environment variables: 

Create a file named.env in the root directory of the project and then add the following codes...

NODE_ENV = production
PORT = 5000
PUBLISHABLE_KEY = pk_test_51HrpB1A3RwZlQsxbFQeHBjXsTwtX3...
STRIPE_SECRET_KEY = sk_test_51HrpB1A3RwZlQsxbaw6ocrnmKPF...

Now create a file named server.js and add the following code ...

const express = require('express');
const dotenv = require('dotenv');
const path = require('path');
dotenv.config();
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.use((_, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header(
	'Access-Control-Allow-Headers',
	'Accept, X-Requested-With, Origin, Content-Type',
  );
  next();
});

// Send stripe publishable key to stripe client side api or react client for safety of the key
app.get('/config', (req, res) => {
  res.json({
	publishableKey: process.env.PUBLISHABLE_KEY,
  });
});


app.use('/create-payment-intent', async (req, res) => {
  const userPrice = parseInt(req.body.price) * 100;
  const intent = await stripe.paymentIntents.create({
	amount: userPrice,
	currency: 'usd',
  });
  res.json({ clientSecret: intent.client_secret, client_id: intent.id });
});

app.post('/confirm-payment', async (req, res) => {
  const paymentType = String(req.body.payment_type);
  if (paymentType == 'stripe') {
	const paymentid = String(req.body.payment_id);
	const confirmIntent = await stripe.paymentIntents
	  .retrieve(paymentid)
	  .then((response) => {
		res.json(response);
	  })
	  .catch();
  }
});

// This code is for the deployment to the Heroku
if (process.env.NODE_ENV === 'production') {
  // To use static assets in the production build
  app.use(express.static(path.join('./', '/frontend/build')));
  // To use the index file like index.html for the production build
  app.get('*', (req, res) =>
	res.sendFile(path.resolve(__dirname, 'frontend', 'build', 'index.html')),
  );
} else {
  app.get('/', (req, res) => {
	res.send('API is running...');
  });
}

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
  console.log(
	`Server is running in ${process.env.NODE_ENV} mode on port ${PORT}`,
  );
});


Here, the route "/config" has been used for the security of the publishable key of the stripe that has been passed from the backend to the frontend.


Create a frontend project:

Go to the root directory of the project and type the following codes to create a frontend with react ...

npx create-react-app frontend

After creating the frontend project with react, install some NPM packages in the frontend directory ...

npm @stripe/stripe-js @stripe/react-stripe-js 
    react-bootstrap bootstrap react-router-dom axios;

Now go to the index.js file of the frontend and integrate the stripe same as below ...
Here, wrap the entire app with a self-invoking function or use an event listener.

// document.addEventListener('DOMContentLoaded', async () => {
(async () => {
const { publishableKey } = await fetch('/config').then((response) =>
  response.json(),
);

const stripePromise = loadStripe(publishableKey);
  

ReactDOM.render(
  // <React.StrictMode>
  <Elements stripe={stripePromise}>
	<App />
  </Elements>,
  // </React.StrictMode>,
  document.getElementById('root'),
);
})();
// });

Now go to the file where we want to add a stripe payment gateway. To execute the stripe payment we have to follow a few steps. I have discussed these steps elaborately.

Step - 1: 
When a customer fillup a web form and click on submit button.

Step - 2: 
Stripe client-side API takes the payment information from the web form and creates a payment method. Stripe client-side API asks Stripe server-side API for a payment intent object through Axios or fetch API.

Step - 3: 
Stripe server-side API creates a payment intent with a secret value. After that, pass the secrete value with payment intent to the Stripe client-side API.

Step - 4: 
Stripe client-side API then receives the secret value from Stripe server-side API and processes the transaction through the stripe payment processor service.

Step - 5: 
Stripe client-side API waits for confirmation from the Stripe payment processor service. 

Step - 6: 
When got the confirmation from the Stripe payment processor service, the Stripe client-side API sends another request to the Stripe server-side API through Axios or fetch API.

Step - 7: 
Now, we can handle the payment confirmation through Stripe server-side API according to our requirements.


Code example as same as follows ...

// import axios from 'axios';

  import { CardElement, useElements, useStripe } from '@stripe/react-stripe-js';
  import React, { useState } from 'react';
  import {
    Button,
    Card,
    Col,
    Container,
    Form,
    Modal,
    Row,
  } from 'react-bootstrap';
  import { Link } from 'react-router-dom';
  import StatusMessages, { useMessages } from './StatusMessages';
  import { useHistory } from 'react-router';

  const CardComponent = () => {
    // useStripe and useElements hooks are used to interact with CardElement
    const stripe = useStripe();
    const elements = useElements();

    // to show error, success, processing, and card complete message
    const [error, setError] = useState(null);
    const [success, setSuccess] = useState(false);
    const [processing, setProcessing] = useState(false);
    const [cardComplete, setCardComplete] = useState(false);
    const [messages, addMessage] = useMessages();

    const history = useHistory();

    // to reset the state
    const reset = () => {
      setError(null);
      setSuccess(false);
      setProcessing(false);
    };

   const handleSubmit = async (event) => {
      event.preventDefault();

      if (!stripe || !elements) {
        return;
      }

      if (cardComplete) {
        setProcessing(true);
        setSuccess(true);
      } else {
        return;
      }

      addMessage('Creating Payment Intent...');

      // Create a payment method
      const payload = await stripe.createPaymentMethod({
        type: 'card',
        card: elements.getElement(CardElement),
        billing_details: {
          name: 'mozahedul1',
          email: 'mozahedul1@gmail.com',
        },
      });

      if (payload.error) {
        return;
      }

      // request stripe server side API for paymentIntent object
      const { clientSecret, client_id } = await fetch('/create-payment-intent', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          price: 1000,
        }),
      }).then((response) => response.json());

      addMessage(`Payment Intent Created: ${clientSecret}, ${client_id}`);

      // After getting client secret and paymentIntent object from stripe server side API,
      // send to payment processor service for payment confirmation
      const { paymentIntent } = await stripe.confirmCardPayment(clientSecret, {
        payment_method: payload.paymentMethod.id,
      });

      addMessage(`PaymentIntent (${paymentIntent.id}): ${paymentIntent.status}`);

      // stripe client side API will wait for confirmation from stripe payment processor service
      // when gets confirmation, then it will send another request to stripe server side
      // API to complete the transaction
      if (paymentIntent.status === 'succeeded') {
        const confirmedPayment = await fetch('/confirm-payment', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            payment_id: client_id,
            payment_type: 'stripe',
          }),
        })
          .then((res) => res.json())
          .catch();

        if (confirmedPayment.status === 'succeeded') {
          reset();
          setProcessing(true);
          setSuccess(true);
        }
      }
    };
    return (
      <Container className="mt-5">
        <Row className="justify-content-center">
          <Col md={6}>
            <Card className="py-3 px-4">
              <Link to="/">
                <Button className="btn-light btn-sm">Home page</Button>
              </Link>
              <h2 className="mb-3">Stripe Payment Form</h2>
              <Form onSubmit={handleSubmit}>
                <Modal show={success}>
                  <Modal.Header>
                    <Modal.Title>Payment Succeeded</Modal.Title>
                  </Modal.Header>
                  <Modal.Body>Your transaction has been completed.</Modal.Body>
                  <Modal.Footer>
                    <Button onClick={() => history.push('/')}>Close</Button>
                  </Modal.Footer>
                </Modal>

                <Modal show={error != null}>
                  <Modal.Header>
                    <Modal.Title>Error! Enter Correct Card Number</Modal.Title>
                  </Modal.Header>
                  <Modal.Body>{error}</Modal.Body>
                  <Modal.Footer>
                    <Button onClick={() => setError(null)}>Close</Button>
                  </Modal.Footer>
                </Modal>

                <Form.Group>
                  <Form.Label htmlFor="card-element">Card</Form.Label>
                  <CardElement
                    id="card-element"
                    onChange={(event) => {
                      setError(event.error && event.error.message);
                      setCardComplete(event.complete);
                    }}
                  />
                </Form.Group>

                <Form.Group className="mt-4">
                  <Button type="submit" disabled={processing || !stripe}>
                    {processing ? 'Processing...' : 'Pay Now'}
                  </Button>
                </Form.Group>
              </Form>
              <StatusMessages messages={messages} />
            </Card>
          </Col>
        </Row>
      </Container>
    );
  };

export default CardComponent;




Post a Comment

Previous Post Next Post