I have a React application with Stripe payments. Under the payments tab in settings I want users to be able to view, add, edit, and remove their payment methods. To accomplish this, my front end calls a Firebase Cloud Function named createSetupIntent. However, whenever I click the button to create a setup intent, I get the following CORS error in the browser console:
Access to fetch at '' from origin 'http://localhost:5173' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
And the request fails with:
POST https://us-central1-lurk-<id>.cloudfunctions/createSetupIntent net::ERR_FAILED
I seem to still be making a direct fetch somewhere that triggers a CORS preflight. How do I configure or fix the calls so that I can properly use the Firebase Functions onCall method (httpsCallable) without hitting these CORS errors?
I replaced any fetch or custom postJSON calls with httpsCallable from the Firebase SDK in PaymentMethodManager.jsx.
I checked my Firebase Functions code (in createSetupIntent) to ensure it’s exported as functions.https.onCall, so it should bypass typical CORS if I am using httpsCallable.
I added cors(...) middleware in some function files to allow origin: true, but I realized that onCall functions might not need that if I only use httpsCallable.
I made sure to redeploy my functions.
I verified my imports are correct:
import { httpsCallable } from 'firebase/functions';
const createSetupIntentFn = httpsCallable(functions, 'createSetupIntent');
Yet the error suggests the function is still being called via a direct POST to cloudfunctions instead of going through the normal httpsCallable pipeline.
I want to ensure all references to createSetupIntent are done via httpsCallable and no direct fetch calls remain.
PaymentMethodManager.jsx
import React, { useState } from 'react';
import { functions } from '../firebase';
import { httpsCallable } from 'firebase/functions';
import { useToast } from '@chakra-ui/react';
export const PaymentMethodManager = () => {
const [showAddCard, setShowAddCard] = useState(false);
const toast = useToast();
const handleAddPaymentMethod = async () => {
try {
// Attempt to create a setup intent via httpsCallable
const createSetupIntentFn = httpsCallable(functions, 'createSetupIntent');
const { data } = await createSetupIntentFn();
if (!data || !data.clientSecret) {
throw new Error('Missing client secret from createSetupIntent response');
}
// Use data.clientSecret with Stripe.js to confirm a card setup
console.log('Setup Intent created:', data.clientSecret);
} catch (error) {
console.error('Error creating setup intent:', error);
toast({
title: 'Error',
description: error.message,
status: 'error',
duration: 3000,
});
}
};
return (
<div>
<button onClick={handleAddPaymentMethod}>
Add Payment Method
</button>
{showAddCard && <StripeCardForm />}
</div>
);
};
createSetupIntent (from my Stripe.js)
exports.createSetupIntent = functions.https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('unauthenticated', 'Must be logged in');
}
try {
console.log('Creating setup intent for user:', context.auth.uid);
// Get user's Stripe customer ID from Firestore
const userDoc = await admin.firestore().collection('userInfo').doc(context.auth.uid).get();
const userData = userDoc.exists ? userDoc.data() : {};
let customerId = userData.stripeCustomerId;
// If no customer ID exists, create a new customer
if (!customerId) {
console.log('No customer ID found, creating new customer');
const customer = await stripe.customers.create({
email: context.auth.token.email,
metadata: {
firebaseUID: context.auth.uid
}
});
customerId = customer.id;
console.log('Created new customer:', customerId);
// Save the customer ID to Firestore
await admin.firestore().collection('userInfo').doc(context.auth.uid).set({
stripeCustomerId: customerId,
email: context.auth.token.email,
updatedAt: admin.firestore.FieldValue.serverTimestamp()
}, { merge: true });
} else {
console.log('Found existing customer:', customerId);
}
// Create a setup intent for the customer
const setupIntent = await stripe.setupIntents.create({
customer: customerId,
payment_method_types: ['card'],
usage: 'off_session',
metadata: {
firebaseUID: context.auth.uid,
customerId: customerId
}
});
console.log('Created setup intent:', setupIntent.id);
return {
clientSecret: setupIntent.client_secret,
customerId: customerId
};
} catch (error) {
console.error('Error in createSetupIntent:', error);
throw new functions.https.HttpsError('internal', error.message);
}
});
getPaymentMethods (from my Stripe.js)
exports.getPaymentMethods = functions.https.onCall(async (data, context) => {
// Add CORS headers if needed
const corsMiddleware = (req, res) => new Promise((resolve, reject) => {
cors(req, res, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
try {
if (context.rawRequest && context.rawResponse) {
await corsMiddleware(context.rawRequest, context.rawResponse);
}
if (!context.auth) {
throw new functions.https.HttpsError('unauthenticated', 'Must be logged in');
}
console.log('Getting payment methods for user:', context.auth.uid);
const userDoc = await admin.firestore().collection('userInfo').doc(context.auth.uid).get();
if (!userDoc.exists) {
console.log('User document not found, creating new document');
await admin.firestore().collection('userInfo').doc(context.auth.uid).set({
email: context.auth.token.email,
createdAt: admin.firestore.FieldValue.serverTimestamp()
});
}
const userData = userDoc.exists ? userDoc.data() : {};
let customerId = userData.stripeCustomerId;
if (!customerId) {
console.log('No customer ID found, creating new customer');
const customer = await stripe.customers.create({
email: context.auth.token.email,
metadata: {
firebaseUID: context.auth.uid
}
});
customerId = customer.id;
console.log('Created new customer:', customerId);
// Save the customer ID to Firestore
await admin.firestore().collection('userInfo').doc(context.auth.uid).update({
stripeCustomerId: customerId
});
} else {
console.log('Found existing customer:', customerId);
}
// Get payment methods
const paymentMethods = await stripe.paymentMethods.list({
customer: customerId,
type: 'card'
});
console.log('Found payment methods:', paymentMethods.data.length);
return {
paymentMethods: paymentMethods.data,
customerId: customerId
};
} catch (error) {
console.error('Error in getPaymentMethods:', error);
throw new functions.https.HttpsError('internal', error.message);
}
});
However, despite using httpsCallable, my developer tools Network tab shows a POST to …cloudfunctions/createSetupIntent with a preflight OPTIONS request that fails with the “No 'Access-Control-Allow-Origin' header” message. I suspect there’s still a leftover direct fetch call somewhere in my code or a misconfiguration in the Firebase Functions setup.
I’m seeking guidance on pinpointing the leftover direct call or properly configuring my onCall function/cors so that the request no longer fails. My goal is to have a fully functional PaymentMethodManager that lists current payment methods and allows for adding new ones without any CORS issues.
I have a React application with Stripe payments. Under the payments tab in settings I want users to be able to view, add, edit, and remove their payment methods. To accomplish this, my front end calls a Firebase Cloud Function named createSetupIntent. However, whenever I click the button to create a setup intent, I get the following CORS error in the browser console:
Access to fetch at 'https://us-central1-lurk-.cloudfunctions.net/createSetupIntent' from origin 'http://localhost:5173' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
And the request fails with:
POST https://us-central1-lurk-<id>.cloudfunctions.net/createSetupIntent net::ERR_FAILED
I seem to still be making a direct fetch somewhere that triggers a CORS preflight. How do I configure or fix the calls so that I can properly use the Firebase Functions onCall method (httpsCallable) without hitting these CORS errors?
I replaced any fetch or custom postJSON calls with httpsCallable from the Firebase SDK in PaymentMethodManager.jsx.
I checked my Firebase Functions code (in createSetupIntent) to ensure it’s exported as functions.https.onCall, so it should bypass typical CORS if I am using httpsCallable.
I added cors(...) middleware in some function files to allow origin: true, but I realized that onCall functions might not need that if I only use httpsCallable.
I made sure to redeploy my functions.
I verified my imports are correct:
import { httpsCallable } from 'firebase/functions';
const createSetupIntentFn = httpsCallable(functions, 'createSetupIntent');
Yet the error suggests the function is still being called via a direct POST to cloudfunctions.net instead of going through the normal httpsCallable pipeline.
I want to ensure all references to createSetupIntent are done via httpsCallable and no direct fetch calls remain.
PaymentMethodManager.jsx
import React, { useState } from 'react';
import { functions } from '../firebase';
import { httpsCallable } from 'firebase/functions';
import { useToast } from '@chakra-ui/react';
export const PaymentMethodManager = () => {
const [showAddCard, setShowAddCard] = useState(false);
const toast = useToast();
const handleAddPaymentMethod = async () => {
try {
// Attempt to create a setup intent via httpsCallable
const createSetupIntentFn = httpsCallable(functions, 'createSetupIntent');
const { data } = await createSetupIntentFn();
if (!data || !data.clientSecret) {
throw new Error('Missing client secret from createSetupIntent response');
}
// Use data.clientSecret with Stripe.js to confirm a card setup
console.log('Setup Intent created:', data.clientSecret);
} catch (error) {
console.error('Error creating setup intent:', error);
toast({
title: 'Error',
description: error.message,
status: 'error',
duration: 3000,
});
}
};
return (
<div>
<button onClick={handleAddPaymentMethod}>
Add Payment Method
</button>
{showAddCard && <StripeCardForm />}
</div>
);
};
createSetupIntent (from my Stripe.js)
exports.createSetupIntent = functions.https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError('unauthenticated', 'Must be logged in');
}
try {
console.log('Creating setup intent for user:', context.auth.uid);
// Get user's Stripe customer ID from Firestore
const userDoc = await admin.firestore().collection('userInfo').doc(context.auth.uid).get();
const userData = userDoc.exists ? userDoc.data() : {};
let customerId = userData.stripeCustomerId;
// If no customer ID exists, create a new customer
if (!customerId) {
console.log('No customer ID found, creating new customer');
const customer = await stripe.customers.create({
email: context.auth.token.email,
metadata: {
firebaseUID: context.auth.uid
}
});
customerId = customer.id;
console.log('Created new customer:', customerId);
// Save the customer ID to Firestore
await admin.firestore().collection('userInfo').doc(context.auth.uid).set({
stripeCustomerId: customerId,
email: context.auth.token.email,
updatedAt: admin.firestore.FieldValue.serverTimestamp()
}, { merge: true });
} else {
console.log('Found existing customer:', customerId);
}
// Create a setup intent for the customer
const setupIntent = await stripe.setupIntents.create({
customer: customerId,
payment_method_types: ['card'],
usage: 'off_session',
metadata: {
firebaseUID: context.auth.uid,
customerId: customerId
}
});
console.log('Created setup intent:', setupIntent.id);
return {
clientSecret: setupIntent.client_secret,
customerId: customerId
};
} catch (error) {
console.error('Error in createSetupIntent:', error);
throw new functions.https.HttpsError('internal', error.message);
}
});
getPaymentMethods (from my Stripe.js)
exports.getPaymentMethods = functions.https.onCall(async (data, context) => {
// Add CORS headers if needed
const corsMiddleware = (req, res) => new Promise((resolve, reject) => {
cors(req, res, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
try {
if (context.rawRequest && context.rawResponse) {
await corsMiddleware(context.rawRequest, context.rawResponse);
}
if (!context.auth) {
throw new functions.https.HttpsError('unauthenticated', 'Must be logged in');
}
console.log('Getting payment methods for user:', context.auth.uid);
const userDoc = await admin.firestore().collection('userInfo').doc(context.auth.uid).get();
if (!userDoc.exists) {
console.log('User document not found, creating new document');
await admin.firestore().collection('userInfo').doc(context.auth.uid).set({
email: context.auth.token.email,
createdAt: admin.firestore.FieldValue.serverTimestamp()
});
}
const userData = userDoc.exists ? userDoc.data() : {};
let customerId = userData.stripeCustomerId;
if (!customerId) {
console.log('No customer ID found, creating new customer');
const customer = await stripe.customers.create({
email: context.auth.token.email,
metadata: {
firebaseUID: context.auth.uid
}
});
customerId = customer.id;
console.log('Created new customer:', customerId);
// Save the customer ID to Firestore
await admin.firestore().collection('userInfo').doc(context.auth.uid).update({
stripeCustomerId: customerId
});
} else {
console.log('Found existing customer:', customerId);
}
// Get payment methods
const paymentMethods = await stripe.paymentMethods.list({
customer: customerId,
type: 'card'
});
console.log('Found payment methods:', paymentMethods.data.length);
return {
paymentMethods: paymentMethods.data,
customerId: customerId
};
} catch (error) {
console.error('Error in getPaymentMethods:', error);
throw new functions.https.HttpsError('internal', error.message);
}
});
However, despite using httpsCallable, my developer tools Network tab shows a POST to …cloudfunctions.net/createSetupIntent with a preflight OPTIONS request that fails with the “No 'Access-Control-Allow-Origin' header” message. I suspect there’s still a leftover direct fetch call somewhere in my code or a misconfiguration in the Firebase Functions setup.
I’m seeking guidance on pinpointing the leftover direct call or properly configuring my onCall function/cors so that the request no longer fails. My goal is to have a fully functional PaymentMethodManager that lists current payment methods and allows for adding new ones without any CORS issues.
Share Improve this question edited Jan 19 at 19:38 halfer 20.4k19 gold badges108 silver badges201 bronze badges asked Jan 19 at 13:11 A UserA User 1 1- 1) Callable type functions handle cors internally. Adding it again manually will only cause problems. 2) We can't help you locate code that we can't see. I suggest keep debugging the code you have now and share with us the results of your debugging (logs, steps to reproduce, and so on). – Doug Stevenson Commented Jan 19 at 14:15
1 Answer
Reset to default 0 If any CORS policy is injected into your backend project, you can put your frontend URL in this configuration. We have shared an example of how to add CORS to our project. I hope this solution is okay. If not, please let us know.
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
builder =>
{
builder.WithOrigins("http://localhost:5173"); // You can add value in configuration file
});
});