如何修复 React 应用程序上 PaymentMethodManager 的 Firebase Functions CORS 错误?

问题描述 投票:0回答:1

我有一个带有 Stripe 支付的 React 应用程序。在设置的付款选项卡下,我希望用户能够查看、添加、编辑和删除他们的付款方式。为了实现此目的,我的前端调用名为 createSetupIntent 的 Firebase 云函数。但是,每当我单击按钮创建设置意图时,我都会在浏览器控制台中收到以下 CORS 错误: 从源“http://localhost:5173”获取“https://us-central1-lurk-.cloudfunctions.net/createSetupIntent”的访问已被 CORS 策略阻止: 对预检请求的响应未通过访问控制检查: 请求的资源上不存在“Access-Control-Allow-Origin”标头。 并且请求失败并显示: POST https://us-central1-lurk-.cloudfunctions.net/createSetupIntent net::ERR_FAILED

我似乎仍然在触发 CORS 预检的地方进行直接获取。如何配置或修复调用,以便可以正确使用 Firebase Functions onCall 方法 (httpsCallable),而不会遇到这些 CORS 错误?

我将 PaymentMethodManager.jsx 中 Firebase SDK 中的所有 fetch 或自定义 postJSON 调用替换为 httpsCallable。

我检查了我的 Firebase Functions 代码(在 createSetupIntent 中)以确保其导出为函数。https.onCall,因此如果我使用 httpsCallable,它应该绕过典型的 CORS。

我在一些函数文件中添加了 cors(...) 中间件以允许 origin: true,但我意识到如果我只使用 httpsCallable,onCall 函数可能不需要它。

我确保重新部署我的职能。

我验证了我的导入是正确的: 从 'firebase/functions' 导入 { httpsCallable }; const createSetupIntentFn = httpsCallable(functions, 'createSetupIntent');

然而,该错误表明该函数仍在通过直接 POST 到 cloudfunctions.net 来调用,而不是通过正常的 httpsCallable 管道。

我想确保对 createSetupIntent 的所有引用都是通过 httpsCallable 完成的,并且不保留直接获取调用。

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(来自我的 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(来自我的 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);
  }
});

但是,尽管使用 httpsCallable,我的开发人员工具“网络”选项卡显示了一个发送到 …cloudfunctions.net/createSetupIntent 的 POST,其中包含预检 OPTIONS 请求,该请求失败并显示“No 'Access-Control-Allow-Origin' header”消息。我怀疑我的代码中的某个地方仍然存在剩余的直接提取调用,或者 Firebase 函数设置中的配置错误。 如果您能提供有关查明剩余直接调用或正确配置我的 onCall 函数/cors 以便请求不再失败的指导,我将不胜感激。我的目标是拥有一个功能齐全的 PaymentMethodManager,它列出当前的付款方式并允许添加新的付款方式而不会出现任何 CORS 问题。 非常感谢任何帮助!

javascript firebase google-cloud-functions cors stripe-payments
1个回答
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
                      });
});
© www.soinside.com 2019 - 2024. All rights reserved.