Node.js+React+MongoDB环境下Stripe嵌入式结账404错误求助
问题
在Node.js、React和MongoDB环境中搭建Stripe嵌入式结账功能时,点击结账按钮后出现以下错误:
- POST http://localhost:5173/api/embedded-checkout 404 (Not Found)
- IntegrationError: fetchClientSecret失败,收到非JSON响应(返回HTML内容)
- 等待client secret超时
相关代码
嵌入式结账路由代码
import express from 'express'; const router = express.Router(); import Stripe from 'stripe'; router.post('/api/embedded-checkout', async (req, res) => { try { const { priceId } = req.body; const session = await Stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [ { price: priceId, }, ], mode: 'subscription', success_url: `${req.headers.origin}/return?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${req.headers.origin}/cancel`, }); res.json({ id: session.id, client_secret: session.client_secret }); } catch (error) { console.error(error); res.status(500).json({ message: error.message }); } }); export default router;
React组件代码
import { loadStripe } from "@stripe/stripe-js"; import { EmbeddedCheckoutProvider, EmbeddedCheckout } from "@stripe/react-stripe-js"; import { useCallback, useRef, useState } from "react"; export default function EmbeddedCheckoutButton() { const stripePromise = loadStripe("pk_live_51P6gXHAOg135X54QQlQBrJkIn5nA79aKUj4s0iHnW9UYMfyo7VnJCT8OYNXSWP9UIHwotu21DsKBxbHEHMfwGhMP00DSgMa86o"); const [showCheckout, setShowCheckout] = useState(false); const modalRef = useRef(null); const fetchClientSecret = useCallback(() => { return fetch("/api/embedded-checkout", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ priceId: "price_1P7D6PAOg135X54QFHUDmSgG" }), }) .then((res) => res.json()) .then((data) => data.client_secret); }, []); const options = { fetchClientSecret }; const handleCheckoutClick = () => { setShowCheckout(true); modalRef.current && modalRef.current.showModal(); }; const handleCloseModal = () => { setShowCheckout(false); modalRef.current && modalRef.current.close(); }; return ( <> <button onClick={handleCheckoutClick}>Open Modal with Embedded Checkout</button> <dialog ref={modalRef} onClose={handleCloseModal}> <h3>Embedded Checkout</h3> {showCheckout && ( <EmbeddedCheckoutProvider stripe={stripePromise} options={options}> <EmbeddedCheckout /> </EmbeddedCheckoutProvider> )} <button onClick={handleCloseModal}>Close</button> </dialog> </> ); }
主服务文件代码
import express from "express"; import mongoose from 'mongoose'; import dotenv from 'dotenv'; import userRoutes from './routes/user.route.js'; import authRoutes from './routes/auth.route.js'; import stripeRoutes from './routes/stripe.route.js'; import stripeEmbeddedRoutes from './routes/embedded-checkout.route.js'; dotenv.config(); mongoose.connect(process.env.MONGO) .then(() => { console.log('Connected to MongoDB'); }) .catch((err) => { console.log(err); }); const app = express(); app.use(express.json()); app.use('/api/user', userRoutes); app.use('/api/auth', authRoutes); app.use('/api/stripe', stripeRoutes); app.use('/api/stripe/success', stripeRoutes); app.use('/api/stripe/cancel', stripeRoutes); app.use ('/api/embedded-checkout', stripeEmbeddedRoutes); app.listen(3000, () => { console.log('server is running on port 3000!'); }); app.use((err, req, res, next) => { const statusCode = err.statusCode || 500; const message = err.message || 'Internal Server Error'; console.log(err); return res.status(statusCode).json({ success: false, message, statusCode, }) })
解决方案
1. 修复404路由不匹配问题
问题根源:
- 前端React运行在
localhost:5173,后端在localhost:3000,前端直接请求/api/embedded-checkout会指向前端服务器,导致404; - 后端路由重复挂载路径:路由文件中定义了
/api/embedded-checkout,主服务又挂载了/api/embedded-checkout前缀,实际有效路径变成/api/embedded-checkout/api/embedded-checkout,完全不匹配。
修复步骤:
前端处理(二选一):
- 方式一:直接指定后端地址
修改React组件中的fetch URL:return fetch("http://localhost:3000/api/embedded-checkout", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ priceId: "price_1P7D6PAOg135X54QFHUDmSgG" }), }) - 方式二:配置Vite代理(推荐,避免跨域)
在vite.config.js中添加代理配置:import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], server: { proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true } } } })
后端路由修改:
将路由文件中的路径改为根路径,因为主服务已经挂载了前缀:
// 原代码 router.post('/api/embedded-checkout', async (req, res) => { ... }) // 修改为 router.post('/', async (req, res) => { ... })
2. 修复Stripe实例初始化缺失秘钥问题
问题根源:
后端代码中未传入Stripe秘钥(sk_开头)就调用API,导致请求失败,返回HTML错误页面而非JSON,引发fetchClientSecret失败;同时line_items中缺失必填的quantity参数,也会导致API调用失败。
修复步骤:
- 在
.env文件中添加Stripe秘钥:STRIPE_SECRET_KEY=sk_your_secret_key_here - 在嵌入式结账路由文件中初始化Stripe实例并补充必填参数:
import express from 'express'; const router = express.Router(); import Stripe from 'stripe'; // 初始化Stripe实例 const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); router.post('/', async (req, res) => { try { const { priceId } = req.body; // 校验必填参数 if (!priceId) { return res.status(400).json({ message: 'priceId参数缺失' }); } // 使用初始化后的stripe实例调用API,补充quantity参数 const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [ { price: priceId, quantity: 1, }, ], mode: 'subscription', success_url: `${req.headers.origin}/return?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${req.headers.origin}/cancel`, }); res.json({ id: session.id, client_secret: session.client_secret }); } catch (error) { console.error(error); res.status(500).json({ message: error.message }); } }); export default router;
3. 等待Client Secret超时问题
此问题是前两个错误的连锁反应,修复上述两个问题后,前端能正常获取client_secret,超时问题会自动解决。
额外优化建议
- 前端Stripe公钥建议从环境变量读取,避免硬编码:
// .env文件 VITE_STRIPE_PUBLIC_KEY=pk_your_public_key_here // React组件中 const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLIC_KEY);
内容的提问来源于stack exchange,提问作者user23114523
相关产品推荐
相关产品推荐

