React+Firebase+Stripe订阅功能报错:addDoc()传入无效price值
问题分析与修复
核心问题
- 拼写错误:在获取价格数据时,你把
priceId拼写成了prceId,导致后续调用loadCheckout时传入的priceId是undefined,触发 Firebase 的数据无效错误。 - 异步迭代问题:使用
forEach结合async/await会导致setProducts在所有价格数据加载完成前就执行,可能造成数据不完整。
修复后的代码
import React, { useEffect, useState } from 'react' import db from '../firebase' import './PlansScreen.css' import { useSelector } from 'react-redux' import { selectUser } from '../features/userSlice' import { loadStripe } from "@stripe/stripe-js" function PlansScreen() { const [products, setProducts] = useState({}) // 初始化为对象更合理 const user = useSelector(selectUser) useEffect(() => { const fetchProducts = async () => { const querySnapshot = await db.collection("products") .where("active", "==", true) .get(); const products = {}; // 使用 Promise.all 等待所有价格数据加载完成 await Promise.all(querySnapshot.docs.map(async productDoc => { products[productDoc.id] = productDoc.data(); const priceSnap = await productDoc.ref.collection('prices').get(); // 假设每个产品对应一个价格,取第一个即可(如有多个价格可调整逻辑) const price = priceSnap.docs[0]; products[productDoc.id].prices = { priceId: price.id, // 修复拼写错误 priceData: price.data() }; })); setProducts(products); }; fetchProducts(); }, []) const loadCheckout = async (priceId) => { // 先校验 priceId 是否存在,避免无效调用 if (!priceId) { alert("无法获取订阅价格,请刷新页面重试"); return; } const docRef = await db .collection('customers') .doc(user.uid) .collection("checkout_sessions") .add({ price: priceId, success_url: window.location.origin, cancel_url: window.location.origin, }); docRef.onSnapshot(async (snap) => { const { error, sessionId } = snap.data(); if (error) { alert(`发生错误:${error.message}`); } if (sessionId) { const stripe = await loadStripe('pk_test_51NSki0SGjMohrz4zbSg4JN71tlTFUkGLWemybtRS31PP94Tzd08kFLUgzTgmxUGjoPQ36S4orri0J2tT8EY5GQZk00NThIU3fu'); stripe.redirectToCheckout({ sessionId }); } }); }; return ( <div className='plansScreen'> {Object.entries(products).map(([productId, productData]) => ( <div key={productId} className="plansScreen__plan"> <div className="planScreen__info"> <h3>{productData.name}</h3> <h5>{productData.description}</h5> </div> <button onClick={() => loadCheckout(productData.prices?.priceId)}> Subscribe </button> </div> ))} </div> ) } export default PlansScreen;
关键修复点说明
- 拼写修正:将
prceId改为priceId,确保价格ID能正确传递到结账函数。 - 异步逻辑优化:用
Promise.all替代forEach处理异步请求,保证所有产品的价格数据加载完成后再更新状态。 - 安全校验:在
loadCheckout中增加priceId存在性检查,避免无效的 Firebase 调用。 - 可选链操作:使用
productData.prices?.priceId防止因数据未加载完成导致的报错。
关于 Firebase 的疑问
Firebase 本身没有问题,错误是因为前端传入了 undefined 的 price 参数。只要确保 priceId 有效,Firebase 的 checkout session 创建就能正常工作。
内容的提问来源于stack exchange,提问作者VAIBHAV WADSAMUDRAKAR
相关产品推荐
相关产品推荐

