使用Stripe+React JS搭建订阅系统时遇'retrieve'未定义错误求助
解决Stripe + React订阅系统错误:"Cannot read properties of undefined (reading 'retrieve')"
错误原因
- 前端Stripe.js不支持客户检索接口:
stripe.customers.retrieve是Stripe后端API专属方法,前端@stripe/stripe-js库未暴露该接口,直接调用会导致stripe.customers为undefined,触发报错。 - 结账模式配置错误:代码中将
mode设为payment(单次付款模式),但订阅系统需使用subscription模式。 - 会话获取方式错误:
stripe.redirectToCheckout.getSession()用法不符合Stripe前端SDK规范,无法直接通过该方式获取会话信息。
解决方案
1. 后端实现客户与订阅查询接口
所有涉及客户、订阅的敏感操作必须放在后端,使用Stripe服务端SDK(如stripe-node)封装接口,前端通过API调用获取数据。
示例Node.js后端代码:
const stripe = require('stripe')('YOUR_SECRET_KEY'); const express = require('express'); const app = express(); app.use(express.json()); // 获取客户订阅状态 app.get('/api/subscription-status/:customerId', async (req, res) => { try { const subscriptions = await stripe.subscriptions.list({ customer: req.params.customerId, status: 'active' }); res.json({ hasActiveSubscription: subscriptions.data.length > 0 }); } catch (error) { res.status(500).json({ error: error.message }); } }); // 创建订阅结账会话 app.post('/api/create-subscription-session', async (req, res) => { const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [ { price: 'YOUR_PRICE_ID', quantity: 1, }, ], mode: 'subscription', success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${req.headers.origin}/cancel`, }); res.json({ id: session.id }); }); app.listen(3001, () => console.log('服务运行在端口3001'));
2. 修正前端代码
移除前端直接调用stripe.customers.retrieve的逻辑,改为调用后端接口;修正结账模式,通过后端创建会话再跳转结账。
修正后的React组件代码:
import { useState, useEffect } from 'react'; import { loadStripe } from '@stripe/stripe-js'; let stripePromise; const getStripe = () => { if (!stripePromise) { stripePromise = loadStripe('YOUR_PUBLISHABLE_KEY'); } return stripePromise; }; const Checkout = () => { const [stripeError, setStripeError] = useState(null); const [isLoading, setLoading] = useState(false); const [subscriptionStatus, setSubscriptionStatus] = useState(null); const [customerId, setCustomerId] = useState(null); // 结账成功后从URL获取会话ID并提取客户ID useEffect(() => { const urlParams = new URLSearchParams(window.location.search); const sessionId = urlParams.get('session_id'); if (sessionId) { const fetchCustomerFromSession = async () => { const stripe = await getStripe(); const session = await stripe.checkout.sessions.retrieve(sessionId); setCustomerId(session.customer); }; fetchCustomerFromSession(); } }, []); // 调用后端接口获取订阅状态 useEffect(() => { if (!customerId) return; const fetchSubscriptionStatus = async () => { try { const response = await fetch(`/api/subscription-status/${customerId}`); const data = await response.json(); setSubscriptionStatus(data.hasActiveSubscription ? 'active' : 'inactive'); } catch (error) { setStripeError('获取订阅状态失败'); } }; fetchSubscriptionStatus(); }, [customerId]); // 调用后端创建会话后跳转结账 const redirectToCheckout = async () => { setLoading(true); try { const response = await fetch('/api/create-subscription-session', { method: 'POST' }); const { id: sessionId } = await response.json(); const stripe = await getStripe(); const { error } = await stripe.redirectToCheckout({ sessionId }); if (error) setStripeError(error.message); } catch (error) { setStripeError('创建结账会话失败'); } finally { setLoading(false); } }; if (stripeError) alert(stripeError); return ( <div className="checkout"> <h1>Stripe 订阅结账</h1> <p className="checkout-title">Design+Code React Hooks 课程</p> <p className="checkout-description">学习如何用React Hooks搭建网站</p> <h1 className="checkout-price">$19/月</h1> {subscriptionStatus === 'active' ? ( <p>您已订阅该课程</p> ) : ( <button className="checkout-button" onClick={redirectToCheckout} disabled={isLoading}> <div className="grey-circle"> <div className="purple-circle"></div> </div> <div className="text-container"> <p className="text">{isLoading ? '加载中...' : '订阅'}</p> </div> </button> )} </div> ); }; export default Checkout;
关键注意事项
- 密钥区分:前端使用可发布密钥(publishable key),后端使用密钥(secret key),绝对不能在前端暴露密钥。
- 订阅产品配置:确保Stripe后台的Price ID为订阅类型(recurring),而非单次购买产品。
- Webhook验证:结账成功后,建议通过Stripe Webhook在后端验证会话状态,避免前端数据篡改风险。
内容的提问来源于stack exchange,提问作者melvin debot
相关产品推荐
相关产品推荐

