/stripe-guides

How to fix CORS issues when using Stripe API on frontend?

Learn how to fix CORS issues with Stripe API on the frontend by using a secure backend proxy, proper CORS configuration, and Stripe.js Elements for safe payments.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to fix CORS issues when using Stripe API on frontend?

How to Fix CORS Issues When Using Stripe API on Frontend

 

Step 1: Understanding CORS Issues with Stripe

 

CORS (Cross-Origin Resource Sharing) issues occur when your frontend JavaScript code tries to make requests to a domain different from the one serving your application. When integrating Stripe, these issues typically arise because Stripe API calls should not be made directly from the frontend due to security concerns.

 

Step 2: Set Up a Backend Server

 

The most secure approach is to set up a backend server that will act as a proxy between your frontend and Stripe. Here's how to create a simple Node.js backend:

// Install required packages
npm install express cors stripe body-parser dotenv

Create a server.js file:

// server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const bodyParser = require('body-parser');

const app = express();
const port = process.env.PORT || 4242;

// Enable CORS for your frontend domain
app.use(cors({
  origin: process.env.FRONTEND\_URL || 'http://localhost:3000',
  credentials: true
}));

// Parse JSON request body
app.use(bodyParser.json());

// Stripe payment intent endpoint
app.post('/create-payment-intent', async (req, res) => {
  try {
    const { amount, currency, payment_method_types } = req.body;
    
    const paymentIntent = await stripe.paymentIntents.create({
      amount,
      currency,
      payment_method_types: payment_method_types || ['card']
    });
    
    res.status(200).json({ clientSecret: paymentIntent.client\_secret });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

 

Step 3: Create Environment Variables

 

Create a .env file in your backend directory:

# .env
STRIPE_SECRET_KEY=sk_test_your_stripe_secret\_key
FRONTEND\_URL=http://localhost:3000
PORT=4242

⚠️ Never expose your Stripe secret key in frontend code.

 

Step 4: Update Your Frontend Code

 

Instead of calling Stripe API directly, your frontend should now communicate with your backend server:

// React example with fetch
import { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';

// Load Stripe.js
const stripePromise = loadStripe('pk_test_your_publishable_key');

// Payment form component
const CheckoutForm = () => {
  const stripe = useStripe();
  const elements = useElements();
  const [error, setError] = useState(null);
  const [processing, setProcessing] = useState(false);

  const handleSubmit = async (event) => {
    event.preventDefault();
    
    if (!stripe || !elements) {
      return;
    }
    
    setProcessing(true);
    
    try {
      // Call your backend instead of Stripe directly
      const response = await fetch('http://localhost:4242/create-payment-intent', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          amount: 1999, // $19.99
          currency: 'usd',
        }),
      });
      
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      
      const data = await response.json();
      
      // Use the client secret from your backend
      const result = await stripe.confirmCardPayment(data.clientSecret, {
        payment\_method: {
          card: elements.getElement(CardElement),
          billing\_details: {
            name: 'Customer Name',
          },
        },
      });
      
      if (result.error) {
        setError(result.error.message);
      } else if (result.paymentIntent.status === 'succeeded') {
        // Payment successful
        console.log('Payment succeeded!');
      }
    } catch (err) {
      setError(err.message);
    }
    
    setProcessing(false);
  };

  return (
    
{error &&
{error}
} ); }; // Wrapper component with Stripe Elements const StripeCheckout = () => { return ( ); }; export default StripeCheckout;

 

Step 5: Configure CORS on Your Hosting Environment

 

If you're deploying your application, make sure your hosting environment is properly configured for CORS:

For Heroku:

// In your package.json, add:
"engines": {
  "node": ">=14"
}

For AWS Lambda:

// If using AWS Lambda with API Gateway
// Configure CORS in API Gateway or in your Lambda function response:

exports.handler = async (event) => {
  // Your function logic
  
  return {
    statusCode: 200,
    headers: {
      "Access-Control-Allow-Origin": "https://your-frontend-domain.com",
      "Access-Control-Allow-Headers": "Content-Type,Authorization",
      "Access-Control-Allow-Methods": "OPTIONS,POST,GET"
    },
    body: JSON.stringify({ message: "Success" })
  };
};

 

Step 6: Using a Proxy in Development

 

If you're using a framework like Create React App, you can add a proxy in your package.json to avoid CORS issues during development:

// In package.json of your React app
{
  "name": "your-app",
  "version": "0.1.0",
  "proxy": "http://localhost:4242",
  // other settings...
}

Then, you can make requests without the full URL:

// Instead of fetch('http://localhost:4242/create-payment-intent', ...)
fetch('/create-payment-intent', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: 1999,
    currency: 'usd',
  }),
});

 

Step 7: Handling CORS in Different Environments

 

Update your CORS configuration to handle different environments:

// server.js
const allowedOrigins = [
  'http://localhost:3000',
  'https://your-production-domain.com',
  'https://your-staging-domain.com'
];

app.use(cors({
  origin: function(origin, callback) {
    // Allow requests with no origin (like mobile apps or curl requests)
    if (!origin) return callback(null, true);
    
    if (allowedOrigins.indexOf(origin) === -1) {
      const msg = 'The CORS policy for this site does not allow access from the specified Origin.';
      return callback(new Error(msg), false);
    }
    
    return callback(null, true);
  },
  credentials: true
}));

 

Step 8: Handling Pre-flight OPTIONS Requests

 

Explicitly handle OPTIONS requests for complex CORS scenarios:

// server.js
app.options('\*', cors()); // Handle pre-flight requests for all routes

// Or for specific routes
app.options('/create-payment-intent', cors());

 

Step 9: Testing Your CORS Configuration

 

Create a simple test function in your frontend to verify your CORS configuration:

// Test function
async function testCorsConfiguration() {
  try {
    const response = await fetch('http://localhost:4242/create-payment-intent', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        amount: 1000,
        currency: 'usd',
      }),
    });
    
    const data = await response.json();
    console.log('CORS test successful:', data);
    return true;
  } catch (error) {
    console.error('CORS test failed:', error);
    return false;
  }
}

// Run the test
testCorsConfiguration().then(success => {
  if (success) {
    console.log('Your API is properly configured for CORS');
  } else {
    console.log('Your API has CORS configuration issues');
  }
});

 

Step 10: Using Stripe.js Elements to Avoid Some CORS Issues

 

Stripe.js Elements handle some aspects of payment collection in a secure iframe, which helps avoid certain CORS issues:

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

const stripePromise = loadStripe('pk_test_your_publishable_key');

function App() {
  return (
    
); } export default App;

 

Troubleshooting Common CORS Issues

 

Issue 1: "No 'Access-Control-Allow-Origin' header is present"

Solution:

// Make sure your backend includes proper headers
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', process.env.FRONTEND\_URL);
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  
  // Handle preflight requests
  if (req.method === 'OPTIONS') {
    return res.status(200).end();
  }
  
  next();
});

 

Issue 2: "Request header field Authorization is not allowed"

Solution:

// Ensure your CORS configuration explicitly allows all required headers
app.use(cors({
  origin: process.env.FRONTEND\_URL,
  credentials: true,
  allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
}));

 

Issue 3: Cookies not being sent with requests

Solution:

// Backend: Ensure credentials are allowed
app.use(cors({
  origin: process.env.FRONTEND\_URL,
  credentials: true
}));

// Frontend: Set credentials option in fetch
fetch('http://localhost:4242/create-payment-intent', {
  method: 'POST',
  credentials: 'include', // This is important for sending cookies
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: 1999,
    currency: 'usd',
  }),
});

 

Conclusion

 

CORS issues with Stripe are common but can be resolved by following proper architecture patterns. Always remember:

  • Never make direct Stripe API calls from your frontend that require your secret key
  • Always use a backend server as a proxy for Stripe API calls
  • Configure CORS properly on your backend server
  • Use environment variables to store sensitive information
  • Take advantage of Stripe.js and Stripe Elements for secure payment form handling

By following these steps, you'll have a secure and working Stripe integration without CORS issues.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022