React+Express集成Stripe:支付成功后前后端反馈方案咨询
新版Stripe Checkout支付结果同步方案(无需WebSocket)
核心思路
新版Stripe Checkout无需依赖WebSocket,可通过前端跳转成功页后主动查询后端状态,或Webhook更新后端订单状态+前端页面加载时验证两种方式,实现支付结果的同步并执行对应逻辑。
方案一:前端跳转成功页后主动查询会话状态
1. 修改后端Checkout会话创建逻辑,传递会话ID到成功页
更新后端/checkout接口的success_url,将Stripe会话ID作为查询参数附加到跳转链接:
app.post("/checkout", async (req, res) => { try { const session = await stripe.checkout.sessions.create({ payment_method_types: ["card"], mode: "payment", line_items: req.body.items.map(({ id, quantity }) => { const storeItem = storeItems.get(id) return { price_data: { currency: "usd", product_data: { name: storeItem.name, }, unit_amount: storeItem.priceInCents, }, quantity: quantity, } }), // 新增会话ID作为查询参数 success_url: "http://localhost:3000/success?session_id={CHECKOUT_SESSION_ID}", cancel_url: "http://localhost:3000/cancel", }) res.json({ url: session.url }) } catch (e) { res.status(500).json({ error: e.message }) } })
2. 后端新增查询会话状态的接口
通过Stripe API查询会话的支付状态,返回给前端:
app.get("/checkout/session-status", async (req, res) => { const { session_id } = req.query; try { const session = await stripe.checkout.sessions.retrieve(session_id); res.json({ status: session.payment_status, // 状态值:paid/unpaid/no_payment_required orderId: session.metadata?.orderId // 若存入元数据可返回订单ID }); } catch (e) { res.status(500).json({ error: e.message }); } });
3. 前端创建Success页面并查询状态
在成功页加载时,从URL提取会话ID并调用后端接口验证支付状态,执行对应逻辑:
// Success.jsx import React, { useEffect, useState } from "react"; export default function Success() { const [paymentStatus, setPaymentStatus] = useState(null); const [error, setError] = useState(null); useEffect(() => { const urlParams = new URLSearchParams(window.location.search); const sessionId = urlParams.get("session_id"); if (sessionId) { fetch(`http://localhost:4242/checkout/session-status?session_id=${sessionId}`) .then(res => res.json()) .then(data => { if (data.status === "paid") { setPaymentStatus("success"); // 执行支付成功逻辑:更新订单、显示成功提示、跳转个人中心等 console.log("支付成功,执行后续操作"); } else { setPaymentStatus("failed"); console.log("支付未完成,请重试"); } }) .catch(err => { setError(err.message); console.log("查询支付状态失败"); }); } }, []); if (error) return <div>查询失败:{error}</div>; if (!paymentStatus) return <div>正在验证支付状态...</div>; return ( <div> {paymentStatus === "success" ? ( <div> <h1>支付成功!</h1> <p>您的订单已确认,感谢购买</p> </div> ) : ( <div> <h1>支付未完成</h1> <p>请返回重新支付</p> </div> )} </div> ); }
方案二:Webhook更新后端状态,前端加载时验证订单
1. Webhook中处理支付成功事件并保存状态
修改后端Webhook逻辑,当Stripe推送checkout.session.completed事件时,将订单状态存入数据库(示例用内存模拟):
// 模拟数据库存储订单状态 const orderStatusMap = new Map(); app.post('/webhook', bodyParser.raw({type: 'application/json'}), async (request, response) => { // 生产环境必须验证Stripe签名,防止伪造请求 // const sig = request.headers['stripe-signature']; // let event; // try { // event = stripe.webhooks.constructEvent(request.body, sig, process.env.STRIPE_WEBHOOK_SECRET); // } catch (err) { // return response.status(400).send(`Webhook Error: ${err.message}`); // } const event = request.body; switch (event.type) { case 'checkout.session.completed': const session = event.data.object; const orderId = session.metadata?.orderId; if (orderId) { orderStatusMap.set(orderId, "paid"); console.log(`订单${orderId}支付成功`); } break; default: console.log(`未处理事件类型:${event.type}`); } response.json({received: true}); });
2. 后端新增查询订单状态的接口
app.get("/order/status", async (req, res) => { const { orderId } = req.query; if (!orderId) { return res.status(400).json({ error: "缺少orderId参数" }); } const status = orderStatusMap.get(orderId); res.json({ status: status || "unknown" }); });
3. 前端传递订单ID并在成功页查询
修改前端handleCheckout,生成订单ID并传递给后端,同时缓存到localStorage:
const handleCheckout = () => { // 生成唯一订单ID(实际项目从后端获取) const orderId = `order_${Date.now()}`; fetch("http://localhost:4242/checkout", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ items: [ { id: 1, quantity: 5 }, { id: 2, quantity: 2 }, ], orderId: orderId // 传递订单ID到后端 }), }) .then(res => { if (res.ok) return res.json(); return res.json().then(json => Promise.reject(json)); }) .then(({ url }) => { localStorage.setItem("currentOrderId", orderId); window.location = url; }) .catch(e => console.log(e.error)); }
在Success页面查询订单状态:
// Success.jsx import React, { useEffect, useState } from "react"; export default function Success() { const [paymentStatus, setPaymentStatus] = useState(null); const [error, setError] = useState(null); useEffect(() => { const orderId = localStorage.getItem("currentOrderId"); if (orderId) { fetch(`http://localhost:4242/order/status?orderId=${orderId}`) .then(res => res.json()) .then(data => { if (data.status === "paid") { setPaymentStatus("success"); console.log("支付成功"); localStorage.removeItem("currentOrderId"); // 清理缓存 } else { setPaymentStatus("pending"); console.log("订单状态待确认"); } }) .catch(err => { setError(err.message); }); } }, []); // 渲染逻辑同方案一 }
注意事项
- 生产环境必须验证Webhook签名:方案二中的签名验证代码已注释,生产环境必须启用,避免恶意请求。
- 状态持久化:示例用内存存储订单状态,实际项目需存入数据库(如MySQL、MongoDB)。
- 会话ID安全性:会话ID可公开传递,但只有后端能通过Stripe API密钥验证其有效性。
内容的提问来源于stack exchange,提问作者Bryam Loaiza
相关产品推荐
相关产品推荐

