Stripe支付处理报错:Unhandled Rejection (TypeError): Failed to fetch求助
Stripe支付流程Fetch请求异常排查与修复
问题描述
对接Stripe支付网关时遇到Unhandled Rejection (TypeError): Failed to fetch错误,无法正常完成支付流程。
前端结账弹窗组件代码
import * as React from 'react'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Typography from '@mui/material/Typography'; import Modal from '@mui/material/Modal'; import products from './product' import { useContext } from 'react'; import { Elements } from "@stripe/react-stripe-js"; import { useState, useEffect} from 'react'; import { loadStripe } from "@stripe/stripe-js"; import cart from '../context/cart' import { CartContext } from '../context/cart'; const style = { position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: "100%", bgcolor: 'background.paper', border: '2px solid #000', boxShadow: 24, p: 4, }; export default function BasicModal() { const [open, setOpen] = React.useState(false); const handleOpen = () => setOpen(true); const handleClose = () => setOpen(false); const stripePromise = loadStripe("pk_test_35p114pH8oNuHX72SmrvsFqh00Azv3ZaIA"); const { cartItems, addToCart, removeFromCart, clearCart, getCartTotal } = useContext(CartContext) useEffect(() => { // storing input name localStorage.getItem("cartItems", JSON.stringify(cartItems)); }, [cartItems]) // payment is processed here const makePayment = async () => { const stripe = await loadStripe("pk_test_35p"); const body = { cartItems }; const headers = { "Content-Type": "application/json", }; const response = await fetch( "localhost:8000/api/create-checkout-session", { method: "POST", headers: headers, body: JSON.stringify(body), } ); const session = await response.json(); const result = stripe.redirectToCheckout({ sessionId: session.id, }); if (result.error) { console.log(result.error); } }; return ( <div> <Button onClick={handleOpen} > ({cartItems.length}) Check Out</Button> <Modal disableScrollLock={true} fullScreen={true} open={open} onClose={handleClose} aria-labelledby="modal-modal-title" aria-describedby="modal-modal-description" > <Box sx={style}> <> <div className="flex-col flex items-center bg-white gap-8 p-10 text-black text-sm"> <h1 className="text-2xl font-bold">Cart</h1> <div className="flex flex-col gap-4"> {cartItems.map((item) => ( <div className="flex justify-between items-center" key={item.id}> <div className="flex gap-4"> <img src={item.imageId} alt={item.title} className="rounded-md h-24" /> <div className="flex flex-col"> <h1 className="text-lg font-bold">{item.title}</h1> <p className="text-gray-600">{item.price}</p> <p className="text-gray-600">{item.description}</p> </div> </div> <div className="flex gap-4"> <button className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700" onClick={() => { addToCart(item) }} > + </button> <p>{item.quantity}</p> <button className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700" onClick={() => { removeFromCart(item) }} > - </button> </div> </div> ))} </div> { cartItems.length > 0 ? ( <div className="flex flex-col justify-between items-center"> <h1 className="text-lg font-bold">Total: ${getCartTotal()}</h1> <button className="px-4 py-2 bg-gray-800 text-white text-xs font-bold uppercase rounded hover:bg-gray-700 focus:outline-none focus:bg-gray-700" onClick={() => { clearCart() }} > Clear cart </button> <Button variant="primary" onClick={makePayment} >Check out </Button> </div> ) : ( <> <h1 className="text-lg font-bold">Your cart is empty</h1> </> ) } </div> <div class="col d-flex justify-content-center"> </div> </> </Box> </Modal> </div> ); }
后端Node服务代码
const cors = require("cors"); const express = require("express"); const stripe = require("stripe")("sMK"); const app = express(); // Middlewares here app.use(express.json()); // Routes here app.get("/", (req, res) => { res.send("Hello World"); }); app.use(cors({origin: "*",})) // Listen app.listen(8000, () => { console.log("Server started at port 8000"); }); app.post("/api/create-checkout-session", async (req, res) => { const { product } = req.body; const session = await stripe.checkout.sessions.create({ payment_method_types: ["card"], line_items: [ { price_data: { currency: "inr", product_data: { name: product.name, }, unit_amount: product.price * 100, }, quantity: product.quantity, }, ], mode: "payment", success_url: "http://localhost:3000/success", cancel_url: "http://localhost:3000/failure", }); res.json({ id: session.id }); });
具体问题与修复方案
1. Fetch请求URL缺少协议头
- 问题:前端
makePayment中请求URL为"localhost:8000/api/create-checkout-session",缺少http://,浏览器会将其解析为相对路径,导致请求失败。 - 修复:修改为
"http://localhost:8000/api/create-checkout-session"
2. Stripe公钥不完整
- 问题:
makePayment里初始化Stripe用了截断的公钥"pk_test_35p",无法正常初始化Stripe实例。 - 修复:复用组件顶部定义的完整公钥
stripePromise,避免重复初始化:const stripe = await stripePromise;
3. 前后端数据字段不匹配
- 问题:前端发送的是
cartItems数组,但后端解构的是单个product对象,导致product.name等取值报错,返回500错误触发fetch失败。 - 修复:后端遍历购物车项生成
line_items,同时统一货币类型(前端显示美元,后端之前用印度卢比):app.post("/api/create-checkout-session", async (req, res) => { const { cartItems } = req.body; if (!cartItems || cartItems.length === 0) { return res.status(400).json({ error: "购物车为空" }); } try { const session = await stripe.checkout.sessions.create({ payment_method_types: ["card"], line_items: cartItems.map(item => ({ price_data: { currency: "usd", product_data: { name: item.title, description: item.description }, unit_amount: item.price * 100, // Stripe使用最小货币单位(如美元为分) }, quantity: item.quantity, })), mode: "payment", success_url: "http://localhost:3000/success", cancel_url: "http://localhost:3000/failure", }); res.json({ id: session.id }); } catch (error) { console.error("Stripe会话创建错误:", error); res.status(500).json({ error: error.message }); } });
4. CORS中间件顺序错误
- 问题:后端
app.use(cors)放在了路由之后,导致CORS配置不生效,跨域请求被浏览器拦截。 - 修复:调整中间件顺序,将CORS放在最前面:
app.use(cors({origin: "*",})); app.use(express.json()); // 之后定义路由... app.get("/", (req, res) => { res.send("Hello World"); }); app.post("/api/create-checkout-session", async (req, res) => { // ...接口逻辑 }); app.listen(8000, () => { console.log("Server started at port 8000"); });
5. Stripe私钥无效
- 问题:后端初始化Stripe用了截断的私钥
"sMK",无法调用Stripe API。 - 修复:替换为你的完整Stripe测试私钥(以
sk_test_开头)。
6. localStorage使用错误
- 问题:
useEffect里用localStorage.getItem存数据,应该用setItem。 - 修复:
useEffect(() => { localStorage.setItem("cartItems", JSON.stringify(cartItems)); }, [cartItems])
7. 缺失错误处理
- 问题:前端未处理HTTP错误状态,后端返回错误时会触发未捕获的Promise拒绝。
- 修复:给
makePayment添加完整的错误捕获:const makePayment = async () => { try { const stripe = await stripePromise; const body = { cartItems }; const headers = { "Content-Type": "application/json", }; const response = await fetch( "http://localhost:8000/api/create-checkout-session", { method: "POST", headers: headers, body: JSON.stringify(body), } ); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "支付会话创建失败"); } const session = await response.json(); const result = stripe.redirectToCheckout({ sessionId: session.id, }); if (result.error) { console.error("Stripe跳转错误:", result.error); alert("支付失败: " + result.error.message); } } catch (error) { console.error("支付错误:", error); alert("支付失败: " + error.message); } };
内容的提问来源于stack exchange,提问作者Tyro
相关产品推荐
相关产品推荐

