React网站Socket.io无法接收后端支付状态数据问题排查
我有一个基于React的网站,正在集成支付引擎。支付引擎页面通过iframe嵌入,因此无法为“Pay”按钮添加onClick事件来处理用户支付操作,若直接在前端使用axios或fetch,会在用户提交支付表单前就触发请求。
支付引擎会在用户提交表单后,向我提供的serviceUrl发送POST请求。我最初接收该请求的后端代码如下:
app.post("/api/payment-status", (req, res) => { console.log("Payment status received"); const {transactionStatus, reason, reasonCode} = JSON.parse(Object.keys(req.body)[0]); console.log("transactionStatus: ", transactionStatus, "reason: ", reason, "reasonCode: ", reasonCode); let paymentData = {}; if (transactionStatus === webConfig.wfpTransactionStatus && reason === webConfig.wfpReason && reasonCode === Number(webConfig.wfpReasonCode)) { console.log("Success!!!"); paymentData = { isSuccess: true, wfpStatus: transactionStatus, wfpReason: reason, msg: "Success.", wfpReasonCode: reasonCode }; } else { console.log("Failed!!!!"); paymentData = { isSuccess: false, wfpStatus: transactionStatus, wfpReason: reason, msg: `Failed: ${reasonCode}!`, wfpReasonCode: reasonCode }; } res.status(200).send("Payment status received."); });
我需要将paymentData对象发送到前端,因此决定使用socket.io实现后端到客户端的数据传输。
后端代码(更新后)
const socketIO = require("socket.io"); const io = socketIO(server, { cors: { origin: "https://www.my-domain.com", methods: ["GET", "POST"] } }); io.on("connection", (socket) => { console.log("A client connected: ", socket.id); socket.on("paymentClientID", (offerID) => { console.log("Client ID on backend: ", offerID); }); socket.on("disconnect", () => { console.log("A client disconnected: ", socket.id); socket.close(); socket.disconnect(); }); }); app.post("/api/payment-status", (req, res) => { console.log("Payment status received"); const {transactionStatus, reason, reasonCode, orderReference} = JSON.parse(Object.keys(req.body)[0]); console.log("transactionStatus: ", transactionStatus, "reason: ", reason, "reasonCode: ", reasonCode, "orderReference: ", orderReference); let paymentData = {}; if (transactionStatus === webConfig.wfpTransactionStatus && reason === webConfig.wfpReason && reasonCode === Number(webConfig.wfpReasonCode) && orderReference) { console.log("Success!!!"); paymentData = { isSuccess: true, wfpOrderReference: orderReference, wfpStatus: transactionStatus, wfpReason: reason, msg: "Success.", wfpReasonCode: reasonCode }; } else { console.log("Failed!!!!"); paymentData = { isSuccess: false, wfpOrderReference: orderReference, wfpStatus: transactionStatus, wfpReason: reason, msg: `Failed: ${reasonCode}!`, wfpReasonCode: reasonCode }; } io.emit("paymentStatus", paymentData); res.status(200).send("Статус платежу отримано"); });
前端代码
import io from "socket.io-client"; const socket = io("https://www.my-domain.com"); useEffect(() => { const wfpPaymentStatusSocket = () => { socket.emit("paymentClientID", offerID); socket.on("paymentStatus", (data) => { console.log("Data: ", data); alert(data); socket.emit("disconnect"); console.log("Disconnecting...."); }); }; wfpPaymentStatusSocket(); }, [socket]);
从服务器日志中,我仅能看到:
A client connected: NUDCr7GnZclJuNYBAAAi
Client ID on backend: 16132880
A client connected: ex9Kg5g-Bejn6oncAAAk
Client ID on backend: 16132880
但前端无法接收paymentStatus数据,请问可能是什么原因?
可能的原因及解决办法
1. 广播消息未定向到目标客户端
当前后端用io.emit("paymentStatus", paymentData)会把消息发给所有连接的客户端,但你需要的是只通知发起当前支付的特定用户。
解决办法:
- 维护一个
offerID与socket.id的映射表,记录每个支付请求对应的客户端连接:
const offerSocketMap = new Map(); io.on("connection", (socket) => { console.log("A client connected: ", socket.id); socket.on("paymentClientID", (offerID) => { console.log("Client ID on backend: ", offerID); // 存储支付订单ID与客户端socket的关联 offerSocketMap.set(offerID, socket.id); }); // 客户端断开时清理映射 socket.on("disconnect", () => { console.log("A client disconnected: ", socket.id); for (const [offerID, socketId] of offerSocketMap.entries()) { if (socketId === socket.id) { offerSocketMap.delete(offerID); break; } } }); });
- 在支付回调接口中,根据
orderReference(假设与offerID对应)找到目标socket,定向发送消息:
app.post("/api/payment-status", (req, res) => { // ... 其他逻辑 const targetSocketId = offerSocketMap.get(orderReference); if (targetSocketId) { // 仅向发起支付的客户端发送状态 io.to(targetSocketId).emit("paymentStatus", paymentData); } // ... 其他逻辑 });
2. 前端Socket实例重复创建或监听逻辑错误
从日志看有两个客户端连接,说明前端可能重复创建了Socket实例,导致实际接收消息的socket不是你监听的那个。另外alert(data)会直接显示[object Object],无法看到具体内容。
解决办法:
用useRef保存Socket实例,避免重复创建,并优化监听逻辑:
import io from "socket.io-client"; import { useEffect, useRef } from "react"; const PaymentComponent = ({ offerID }) => { const socketRef = useRef(null); useEffect(() => { // 仅创建一次Socket连接 if (!socketRef.current) { socketRef.current = io("https://www.my-domain.com"); } const socket = socketRef.current; const handlePaymentStatus = (data) => { console.log("Data: ", data); alert(JSON.stringify(data)); // 转成字符串才能看到对象内容 socket.off("paymentStatus", handlePaymentStatus); // 移除监听避免重复触发 socket.disconnect(); console.log("Disconnecting...."); }; socket.emit("paymentClientID", offerID); socket.on("paymentStatus", handlePaymentStatus); // 组件卸载时清理资源 return () => { if (socket) { socket.off("paymentStatus"); socket.disconnect(); } }; }, [offerID]); // ... 组件其他代码 };
3. CORS配置不完整
Socket.IO的CORS配置可能需要额外选项,比如允许凭证传递,或者origin匹配存在问题(比如域名带端口但配置中没写)。
解决办法:
更新Socket.IO的CORS配置:
const io = socketIO(server, { cors: { origin: "https://www.my-domain.com", methods: ["GET", "POST"], credentials: true // 如果前端需要携带cookie等凭证则开启 } });
4. 支付回调接口未被触发
如果服务器日志中没有Payment status received这条记录,说明支付引擎根本没调用你的回调接口,自然不会发送Socket消息。
解决办法:
- 核对支付引擎配置的serviceUrl是否正确,确保接口能被正常访问。
- 在回调接口中添加错误捕获,排查解析请求体时的问题:
app.post("/api/payment-status", (req, res) => { try { console.log("Payment status received"); const payload = JSON.parse(Object.keys(req.body)[0]); console.log("Received payload: ", payload); // ... 其他逻辑 } catch (err) { console.error("解析支付状态失败:", err); res.status(400).send("无效的请求数据"); } });
内容的提问来源于stack exchange,提问作者Hunter91151

