Paystack回调功能异常导致用户余额未增加问题求助
问题诊断与修复方案
核心问题梳理
你的代码存在几个关键问题,导致用户余额无法更新:
- JWT Token生成错误:错误解构用户ID,导致Token中未正确携带有效用户ID
- 异步操作未等待:回调函数中的数据库操作未使用
await,导致操作可能未完成就返回响应 - Paystack初始化逻辑冲突:
await与then混用,导致响应重复发送且变量赋值无效 - 不必要的
await使用:req.query.reference是同步值,无需await
分步修复代码
1. 修复depositFunds函数
exports.depositFunds = async (req, res) => { try { const { amount, email, reference } = req.body; // 直接获取用户ID,req.user._id本身就是用户ID值,无需二次解构 const userId = req.user._id; const jwtToken = jwt.sign({ userId }, process.env.JWT_SECRET); // 统一用async/await,避免then和await混用导致逻辑混乱 const response = await paystack.transaction.initialize({ amount, email, reference, metadata: { sender: userId, receiver: userId, jwtToken: jwtToken, }, callback_url: "http://localhost:8000/api/transactions/payment-callback", }); res.send(response.data); } catch (error) { console.error(error); res.status(500).json({ error: "Failed to initialize payment" }); } };
修复点:
- 移除错误的
const { _id } = user;,直接使用req.user._id作为用户ID - 统一使用
async/await并添加错误捕获,避免then和await混用的逻辑冲突 - 删除多余的
res.json(transaction),防止重复发送HTTP响应
2. 修复paymentCallback函数
exports.paymentCallback = async (req, res) => { try { // req.query.reference是同步获取的参数,不需要await const reference = req.query.reference; // 将Paystack的回调式验证转为Promise,适配async/await语法 const verifyTransaction = () => { return new Promise((resolve, reject) => { paystack.transaction.verify(reference, (error, body) => { if (error) reject(error); else resolve(body); }); }); }; const body = await verifyTransaction(); const { data } = body; if (data.status !== "success") { console.log("Payment was not successful"); return res.redirect("/error"); } const token = data.metadata.jwtToken; const decodedToken = jwt.verify(token, process.env.JWT_SECRET); const userId = decodedToken.userId; if (!userId) { return res.status(401).json({ error: "Invalid token" }); } // 关键:Paystack的amount是以最小货币单位计算的(比如奈拉对应Kobo),需转换为实际金额 const actualAmount = data.amount / 100; // 等待交易记录保存完成 const newTransaction = new Transactions({ sender: userId, receiver: userId, amount: actualAmount, type: "deposit", reference: data.reference, // 使用Paystack返回的真实交易参考号 status: "success", }); await newTransaction.save(); // 等待用户余额更新完成,{new: true}可选,用于获取更新后的用户文档 await User.findByIdAndUpdate(userId, { $inc: { balance: actualAmount }, }, { new: true }); res.send({ message: "Transaction successful", data: newTransaction, success: true, }); // res.redirect("http://localhost:3000/user/transactions"); } catch (error) { console.error(error); res.redirect("/error"); } };
修复点:
- 将Paystack的回调验证转为Promise,使用
async/await保证操作执行顺序 - 为所有异步数据库操作(
save()和findByIdAndUpdate())添加await,确保操作完成后再返回响应 - 处理Paystack金额单位转换,避免余额出现100倍的错误
- 使用Paystack返回的真实交易参考号,替代固定字符串
- 添加全局错误捕获,防止未处理异常导致服务崩溃
额外建议
- Webhook签名验证:建议配置Paystack Webhook并验证签名,防止恶意请求篡改交易数据
- 数据库事务:在更新余额和保存交易记录时使用数据库事务(如MongoDB的session),确保两个操作原子性,避免数据不一致
- 日志增强:在关键步骤添加详细日志,方便后续问题排查
内容的提问来源于stack exchange,提问作者Kelvin Henry
相关产品推荐
相关产品推荐

