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

Firebase类型错误求助:Stripe结账功能点击触发报错排查

问题:React集成Stripe结账时Firebase类型错误

我是Stack Overflow新手,在React项目中实现Stripe结账功能,意图引导用户跳转到Stripe结账页面,但点击loadCheckout()按钮时,持续出现错误:

Uncaught (in promise) FirebaseError: Expected type 'pa', but it was: a custom Promise object

错误截图:
错误截图

相关代码:

import { collection, getDocs, where, query, addDoc } from 'firebase/firestore/lite';
import { onSnapshot } from 'firebase/firestore';
import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { selectUser } from '../features/userSlice';
import db from '../firebase';
import './Plans.css';
import { loadStripe } from '@stripe/stripe-js';

const Plans = () => {

    const [products, setProducts] = useState([]);
    const user = useSelector(selectUser);
    
    useEffect(() => {
        if(collection) {
            getDocs(query(collection(db, 'products'), where('active', "==", true)))
            .then((querySnapshot) => {
                const products = {}
                querySnapshot.forEach(async (productDoc) => {
                    products[productDoc.id] = productDoc.data();
                    const priceSnap = await getDocs(query(collection(db, `products/${productDoc.id}/prices`)));
                    priceSnap.docs.forEach((price) => {
                        products[productDoc.id].prices = {
                            priceId: price.id,
                            ...price.data(),
                        };
                    });
                    setProducts(products);
                });
                return querySnapshot;
            });
        }
    }, []);

    
    const loadCheckout = async (priceId) => {
        const firstQ = collection(db, `customers/${user.uid}/checkout_sessions`);
        const secondQ = query(
            addDoc(firstQ, {
                price: priceId,
                success_url: window.location.origin,
                cancel_url: window.location.origin,
            })
        );
        onSnapshot(secondQ, async (snap) => {
            const { error, sessionId } = snap.data();
            if(error) {
                // show error to the customer
                // inspect the cloud function logs in the firebase console.
                alert(`An error occurred: ${error.message}`);
            }
            if (sessionId) {
                // we have a stripe checkout url, let's redirect
                const stripe = await loadStripe(
                    'pk_test_51LnQj5K41BCUjAV1QSW7AAeX3XMzWJZLUNPNPKPKtHojs4wj9ECSky68tcluQQkCGMsgrA6wMQ58C0uYEWdUALja00CNIYgnnb');
                stripe.redirectToCheckout({ sessionId });
            }
        });
    }

  return (
    <div className='plans'>
      {Object.entries(products).map(([productId, productData], index) => {
        // add some logic to check if the user's subscription is active...
        return (
            <div key={index} className='plans__plan'>
                <div className='plans__info'>
                    <h5>{productData.name}</h5>
                    <h6>{productData.description}</h6>
                </div>
                <button onClick={() => loadCheckout(productData.prices.priceId)}>
                    Subscribe
                </button>
            </div>
        );
      })}
    </div>
  )
}

export default Plans;

解决方案

1. 核心错误原因

loadCheckout函数中误用了Firebase API:

  • addDoc()返回的是Promise对象,不是集合或查询对象,不能作为query()的参数传入
  • onSnapshot()需要接收集合/查询/文档引用,不能直接传入Promise

2. 修正loadCheckout函数逻辑

先创建checkout session文档,再监听该文档的变化:

const loadCheckout = async (priceId) => {
  // 1. 创建checkout session文档
  const checkoutSessionRef = await addDoc(
    collection(db, `customers/${user.uid}/checkout_sessions`),
    {
      price: priceId,
      success_url: window.location.origin,
      cancel_url: window.location.origin,
    }
  );

  // 2. 监听该文档的变化
  const unsubscribe = onSnapshot(checkoutSessionRef, async (snap) => {
    const { error, sessionId } = snap.data() || {};
    if (error) {
      alert(`An error occurred: ${error.message}`);
      unsubscribe(); // 出错后取消监听
    }
    if (sessionId) {
      const stripe = await loadStripe(
        'pk_test_51LnQj5K41BCUjAV1QSW7AAeX3XMzWJZLUNPNPKPKtHojs4wj9ECSky68tcluQQkCGMsgrA6wMQ58C0uYEWdUALja00CNIYgnnb'
      );
      stripe.redirectToCheckout({ sessionId });
      unsubscribe(); // 跳转后取消监听
    }
  });
};

3. 修复useEffect中的异步问题

原代码中forEach(async ...)会导致setProducts多次执行且顺序不可控,改用Promise.all处理所有异步操作:

useEffect(() => {
  const fetchProducts = async () => {
    const productsQuery = query(collection(db, 'products'), where('active', "==", true));
    const querySnapshot = await getDocs(productsQuery);
    
    const products = {};
    await Promise.all(querySnapshot.docs.map(async (productDoc) => {
      products[productDoc.id] = productDoc.data();
      const priceSnap = await getDocs(query(collection(db, `products/${productDoc.id}/prices`)));
      priceSnap.docs.forEach((price) => {
        products[productDoc.id].prices = {
          priceId: price.id,
          ...price.data(),
        };
      });
    }));
    
    setProducts(products);
  };

  fetchProducts();
}, []);

额外提示

生产环境中建议通过Firebase云函数生成Stripe checkout session,不要在前端直接操作,避免暴露敏感配置。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 10:50:22