最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How do I fix Firebase Functions CORS error for PaymentMethodManager on my React app? - Stack Overflow

programmeradmin1浏览0评论

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
Add a comment  | 

1 Answer 1

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
                      });
});
发布评论

评论列表(0)

  1. 暂无评论