You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React集成Firebase与Stripe时priceId属性未定义问题求助

问题排查:React + Firebase Firestore + Stripe支付中priceId访问异常

问题现象

基于ReactJS和Firebase Cloud Firestore实现Stripe支付时,出现以下异常:

  • 从Firestore获取产品数据并构建包含priceId的对象后,组件渲染时用console.log(productData?.prices?.priceId)能正常输出priceId
  • 点击支付按钮调用loadCheckout时,提示productData.prices.priceId未定义,无法触发支付流程

产品对象结构示例:

{"tax_code": null,"images": [],"name": "Premium Plan","description": "4k + HDR + 5 Family Members","active": true,"role": null,"metadata": {},"prices": {"priceId": "price_1LpD2rHO1lI6EMwl58lLNxRo","product": "prod_MYJgCtqv3JyCPn","metadata": {},"unit_amount": 15000,"transform_quantity": null,"interval_count": 1,"tiers_mode": null,"description": "150","trial_period_days": null,"type": "recurring","active": true,"tiers": null,"interval": "month","tax_behavior": "unspecified","currency": "dkk","recurring": {"trial_period_days": null,"usage_type": "licensed","interval_count": 1,"interval": "month","aggregate_usage": null},"billing_scheme": "per_unit"}}

核心原因

  1. 异步时序错误:fetchData中使用querySnapshot.forEach(async (productDoc) => {}),但forEach不支持异步函数的等待,导致setProducts(products)在子查询(获取prices数据)完成前就执行,初始状态下products对象没有prices属性,后续异步完成后才更新,但按钮点击可能在更新前触发。
  2. prices赋值逻辑问题:subQDetails.docs.map(...)会遍历所有price文档,若存在多个price会覆盖prices属性;且未处理无price文档的边界情况。
  3. Firestore监听错误:loadCheckout中用query(addDoc(...))是错误用法,addDoc返回单个文档引用,而非查询,无法用query包裹,导致监听逻辑失效。

修复方案

步骤1:修正数据获取的异步等待逻辑

将forEach替换为map,结合Promise.all等待所有子查询完成后再更新状态:

const fetchData = async () => {
  const q = query(collection(db, "products"), where("active", "==", true));
  const querySnapshot = await getDocs(q);
  
  // 用map生成异步任务数组,Promise.all等待所有任务完成
  const productsArray = await Promise.all(querySnapshot.docs.map(async (productDoc) => {
    const productData = productDoc.data();
    const subQuery = query(collection(db, `products/${productDoc.id}/prices`));
    const subQDetails = await getDocs(subQuery);
    
    // 取第一个price文档(根据业务需求调整,若多个可存数组)
    const priceDoc = subQDetails.docs[0];
    if (priceDoc) {
      productData.prices = {
        priceId: priceDoc.id,
        ...priceDoc.data(),
      };
    }
    
    return [productDoc.id, productData];
  }));
  
  // 转换为对象后更新状态
  const products = Object.fromEntries(productsArray);
  setProducts(products);
};

步骤2:修复按钮点击时的属性访问

给按钮点击事件中的属性访问添加可选链,避免未定义报错:

<button onClick={() => loadCheckout(productData?.prices?.priceId)}>Subscribe</button>

步骤3:修正Stripe支付的Firestore监听逻辑

addDoc返回单个文档引用,直接监听该引用即可:

const loadCheckout = async (priceId) => {
  if (!priceId) return; // 提前过滤无效priceId
  
  const checkoutRef = await addDoc(collection(db, `customers/${user.uid}/checkout_sessions`), {
    price: priceId,
    success_url: window.location.origin,
    cancel_url: window.location.origin,
  });
  
  onSnapshot(checkoutRef, async (snap) => {
    const { error, sessionId } = snap.data() || {};
    if (error) {
      alert(`An error occured: ${error.message}`);
      console.log(error.message);
    }
    if (sessionId) {
      const stripe = await loadStripe("pk_test_51LpCKmHO1lI6EMwlLvPQQSBZ2A6JTa6PmqXwLcYstnaf04qaG7CQYGW3DYWmrIL4QjzXfKQnDQaIeWSjrHyLx0fY000otcrJ7t");
      stripe.redirectToCheckout({ sessionId });
    }
  });
};

完整修正代码

function PlanScreen() {
  const [products, setProducts] = useState({}); // 初始值改为空对象,匹配后续结构
  const user = useSelector(selectUser);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    const q = query(collection(db, "products"), where("active", "==", true));
    const querySnapshot = await getDocs(q);
    
    const productsArray = await Promise.all(querySnapshot.docs.map(async (productDoc) => {
      const productData = productDoc.data();
      const subQuery = query(collection(db, `products/${productDoc.id}/prices`));
      const subQDetails = await getDocs(subQuery);
      
      const priceDoc = subQDetails.docs[0];
      if (priceDoc) {
        productData.prices = {
          priceId: priceDoc.id,
          ...priceDoc.data(),
        };
      }
      
      return [productDoc.id, productData];
    }));
    
    const products = Object.fromEntries(productsArray);
    setProducts(products);
  };

  const loadCheckout = async (priceId) => {
    if (!priceId || !user?.uid) return;
    
    const checkoutRef = await addDoc(collection(db, `customers/${user.uid}/checkout_sessions`), {
      price: priceId,
      success_url: window.location.origin,
      cancel_url: window.location.origin,
    });

    onSnapshot(checkoutRef, async (snap) => {
      const { error, sessionId } = snap.data() || {};
      if (error) {
        alert(`An error occured: ${error.message}`);
        console.log(error.message);
      }
      if (sessionId) {
        const stripe = await loadStripe("pk_test_51LpCKmHO1lI6EMwlLvPQQSBZ2A6JTa6PmqXwLcYstnaf04qaG7CQYGW3DYWmrIL4QjzXfKQnDQaIeWSjrHyLx0fY000otcrJ7t");
        stripe.redirectToCheckout({ sessionId });
      }
    });
  };

  return (
    <div className="planScreen">
      {Object.entries(products).map(([productId, productData]) => {
        return (
          <div className="planScreen_plan" key={productId}>
            <div className="planScreen_info">
              <h5>{productData.name}</h5>
              <h6>{productData.description}</h6>
              <h6>{productData?.prices?.priceId}</h6>
            </div>
            <button onClick={() => loadCheckout(productData?.prices?.priceId)}>Subscribe</button>
          </div>
        );
      })}
    </div>
  );
}

内容的提问来源于stack exchange,提问作者Burnir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 00:45:49