You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Paystack回调功能异常导致用户余额未增加问题求助

问题诊断与修复方案

核心问题梳理

你的代码存在几个关键问题,导致用户余额无法更新:

  1. JWT Token生成错误:错误解构用户ID,导致Token中未正确携带有效用户ID
  2. 异步操作未等待:回调函数中的数据库操作未使用await,导致操作可能未完成就返回响应
  3. Paystack初始化逻辑冲突:await与then混用,导致响应重复发送且变量赋值无效
  4. 不必要的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返回的真实交易参考号,替代固定字符串
  • 添加全局错误捕获,防止未处理异常导致服务崩溃

额外建议

  1. Webhook签名验证:建议配置Paystack Webhook并验证签名,防止恶意请求篡改交易数据
  2. 数据库事务:在更新余额和保存交易记录时使用数据库事务(如MongoDB的session),确保两个操作原子性,避免数据不一致
  3. 日志增强:在关键步骤添加详细日志,方便后续问题排查

内容的提问来源于stack exchange,提问作者Kelvin Henry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 21:23:12