使用Stripe+Firebase实现订阅购买时遭遇CORS错误求助
调用Stripe订阅购买函数时,浏览器抛出CORS错误:
Access to fetch at 'https://us-central1-smallgistics-cd63d.cloudfunctions.net/createStripeSubscription' from origin 'https://smallgistics.com' 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. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
已反复修改前后端代码,参考过教程及VS Code Copilot,问题仍未解决,求可行方案。
服务端代码(Firebase Cloud Functions)
const functions = require('firebase-functions'); const admin = require('firebase-admin'); const cors = require('cors'); const stripe = require('stripe')('sk_test_51NdbMmEls8dLhARfYdtPdL3o7oSszlYaZJE7GMYhvPtpjgGXjGYXLPlEwhD5n4sXZFR0NtaAar1qQQzG6PKEnI9M000ccfgqwX'); var serviceAccount = require('./smallgistics-cd63d-firebase-adminsdk-dev8o-b099ac3121.json'); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), databaseURL: 'https://smallgistics-cd63d.firebaseio.com' }); exports.createStripeSubscription = functions.https.onRequest((request, response) => { const corsHandler = cors({origin: true}); corsHandler(request, response, async () => { const userId = request.body.userId; // The ID of the user in your Firebase auth const productId = 'prod_Pw1OIYAqg3JuG8'; // The ID of the product in Stripe try { // Retrieve the Stripe customer ID from Firestore const doc = await admin.firestore().collection('users').doc(userId).get(); const customer = doc.data().stripeCustomerId; // Create the subscription const subscription = await stripe.subscriptions.create({ customer: customer, items: [{ product: productId }], }); response.send({ subscriptionId: subscription.id }); } catch (error) { console.error(error); response.status(500).send(error); } }); });
客户端代码
<script> // Your web app's Firebase configuration var firebaseConfig = { apiKey: "AIzaSyBmLNOykJH7eAaSxQZsD5P7IlQhUQtBm5k", authDomain: "smallgistics-cd63d.firebaseapp.com", projectId: "smallgistics-cd63d", storageBucket: "smallgistics-cd63d.appspot.com", messagingSenderId: "641898304430", appId: "1:641898304430:web:9a7aecf59ea12dc0822e3f", measurementId: "G-WR83XXQHC0" }; // Initialize Firebase firebase.initializeApp(firebaseConfig); window.onload = function() { // Set up an auth state changed listener firebase.auth().onAuthStateChanged(user => { if (user) { // User is signed in, set up the button click handler const subscribeButton = document.getElementById('subscribeButton'); if (subscribeButton) { subscribeButton.addEventListener('click', () => { // Determine the URL to use based on the environment const functionURL = location.hostname === 'localhost' || location.hostname === '127.0.0.1' ? 'http://127.0.0.1:5002/smallgistics-cd63d/us-central1/createStripeSubscription' : 'https://us-central1-smallgistics-cd63d.cloudfunctions.net/createStripeSubscription'; // Call the Firebase function fetch(functionURL, { method: 'POST', body: JSON.stringify({ userId: user.uid }), headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error)); }); } else { console.error('The element with ID subscribeButton does not exist.'); } } else { // User is signed out console.log('No user is signed in.'); } }); }; </script>
解决方案
1. 修正CORS中间件的初始化方式
当前代码在函数内部初始化cors handler,可能导致预请求(OPTIONS)无法被正确处理。改为直接将cors作为中间件包裹整个函数:
// 替换原来的函数定义 exports.createStripeSubscription = functions.https.onRequest(cors({origin: true}), async (request, response) => { const userId = request.body.userId; const productId = 'prod_Pw1OIYAqg3JuG8'; try { const doc = await admin.firestore().collection('users').doc(userId).get(); const customer = doc.data().stripeCustomerId; const subscription = await stripe.subscriptions.create({ customer: customer, items: [{ product: productId }], }); response.send({ subscriptionId: subscription.id }); } catch (error) { console.error(error); response.status(500).send(error); } });
2. 显式指定允许的Origin(可选,更安全)
如果不想允许所有来源,可将{origin: true}替换为具体域名列表:
cors({origin: ['https://smallgistics.com', 'http://localhost:5002']})
3. 检查函数部署状态
- 确保修改后的代码已重新部署:运行
firebase deploy --only functions:createStripeSubscription - 查看Firebase控制台的函数日志,确认预请求(OPTIONS)是否被正常处理
- 检查函数是否处于错误状态(比如依赖缺失、初始化失败)
4. 确认CORS依赖安装
在函数目录下运行npm install cors --save,确保cors依赖已正确安装并被package.json记录
5. 客户端请求优化(可选)
显式指定请求模式为cors(默认已生效,可避免潜在环境问题):
fetch(functionURL, { method: 'POST', mode: 'cors', body: JSON.stringify({ userId: user.uid }), headers: { 'Content-Type': 'application/json' } })
内容的提问来源于stack exchange,提问作者psimonson

