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
相关产品推荐
相关产品推荐

