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

集成SSLCommerz支付后为何出现UnhandledPromiseRejectionWarning错误?

SSLCommerz支付成功回调报错:Cannot read property '_id' of undefined

问题场景

集成SSLCommerz支付网关后,用户完成支付后无法跳转到仪表板,控制台抛出错误:

UnhandledPromiseRejectionWarning: TypeError: Cannot read property '_id' of undefined

已配置cookie parser,但问题依然存在。

支付网关代码

const express = require( 'express' );
const router = express.Router();
const SSLCommerzPayment = require( "sslcommerz" );
const User = require( '../models/user' );
const crypto = require( 'crypto' );


router.get( '/charge', isLoggedIn, ( req, res ) =>{
  User.findById( req.user._id, ( err, user ) => { res.render( 'payments/recharge', { user } ); } )
})

router.post( '/charge', isLoggedIn, async ( req, res ) =>
{

  const id = crypto.randomBytes( 16 ).toString( "hex" );
  const { amount } = req.body;
  console.log( id + " " + amount );
  const user = await User.findById( req.user._id )
  console.log( user.PhoneNumber );
  const data = {
    //transaction info
    total_amount: amount,
    currency: 'BDT',
    tran_id: id,
    cus_name: user.username,
    cus_email: user.email,
    cus_phone: user.PhoneNumber,

    //redirection urls
    success_url: `http://localhost:8000/payment/ssl-payment-success`,
    fail_url: `http://localhost:8000/payment/ssl-payment-fail`,
    cancel_url: `http://localhost:8000/payment/ssl-payment-cancel`,
    ipn_url: `http://localhost:8000/payment/ssl-payment-notification`,


    //will not be needed for our A2F but if we delete it sslcommerz will not work
    shipping_method: 'No',
    product_name: 'Computer.',
    product_category: 'Electronic',
    product_profile: 'general',
  };
  const sslcommerz = new SSLCommerzPayment( process.env.STORE_ID, process.env.STORE_PASSWORD, false ) //true for live default false for sandbox
  sslcommerz.init( data ).then( data =>
  {
    console.log( data.GatewayPageURL )
    if ( data.GatewayPageURL )
    {
      return res.status( 200 ).redirect( data.GatewayPageURL );
    }
    else
    {
      return res.status( 400 ).json( {
        message: "Session was not successful"
      } );
    }
  } );
} );

router.post("/ssl-payment-notification", async (req, res) => {

  return res.status(200).json(
    {
      data: req.body,
      message: 'Payment notification'
    }
  );
})

router.post("/ssl-payment-success", async (req, res) => {

  const user = await User.findById( req.user._id )
  user.wallet += Number(req.body.store_amount);
  user.save();
  return res.redirect( '/user/dashboard' );
})

router.post("/ssl-payment-fail", async (req, res) => {

  return res.status(200).json(
    {
      data: req.body,
      message: 'Payment failed'
    }
  );
})

router.post("/ssl-payment-cancel", async (req, res) => {

  return res.status(200).json(
    {
      data: req.body,
      message: 'Payment cancelled'
    }
  );
})

function isLoggedIn( req, res, next )
{
  // console.log(req.isAuthenticated());
  if ( req.isAuthenticated() )
  {
    return next();
  }
  res.redirect( '/user/login' );
}

module.exports = router;

app.js代码

//App creation
const express = require("express");
const app = express();

//port setup
require('dotenv').config();
const port = process.env.PORT || 8080;

//body parser configutation
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({extended: true}));

//morgan configuration
const morgan = require('morgan');
app.use(morgan('dev'));

const cookieParser = require('cookie-parser')
app.use(cookieParser(process.env.SESSION_SECRET));
app.use(require('express-session')({
    secret: process.env.SESSION_SECRET,
    cookie : 
    {
        expires: false
    },
    resave: true,
    saveUninitialized: true
}));

//passport configuration
const passport = require('passport');
app.use(passport.initialize());
app.use(passport.session());

const localStrategy = require('passport-local');
const User = require('./models/user');
passport.use(new localStrategy(User.authenticate()));
passport.serializeUser(User.serializeUser());
passport.deserializeUser(User.deserializeUser());


//router configuration
const userRoutes = require('./routes/user');
app.use('/user',userRoutes);
const loanRoutes = require('./routes/loan');
app.use('/loan',loanRoutes);
const adminRoutes = require('./routes/admin');
app.use('/admin',adminRoutes);
const paymentRoutes = require('./routes/payment');
app.use('/payment',paymentRoutes);

//database connection
const mongoose = require('mongoose');
mongoose.connect("mongodb+srv://sifat:12345@cluster0.xtknfmw.mongodb.net/test", function(err) {
    if (err) {
        console.log("Database Not Connected", err);
    } else {
        console.log("Database Connected")
    }
});
mongoose.set('useFindAndModify', false);






//Other usage for application
app.use(express.static("public"));

app.use((req,res,next)=>{
    res.locals.currentUser = req.user;
    next();
});
app.set('view engine','ejs');

//Home page
app.get('/',(req,res)=>{res.render('home');})

//Server Start
app.listen(port, () => {console.log(`Server is running on port ${port}`);});

问题原因

错误出在/ssl-payment-success路由,req.user是undefined,导致读取req.user._id失败。核心原因是:
SSLCommerz支付完成后,是直接向你的服务器发起POST请求,这个请求不会携带用户的浏览器会话Cookie,因此Passport无法识别当前用户,req.user为空。

解决方法

方法一:通过自定义参数传递用户ID(推荐)

在创建支付订单时,把用户ID作为自定义字段传给SSLCommerz,回调时从请求参数里获取,不依赖会话:

  1. 修改支付创建代码,添加用户ID字段:
const data = {
  // ... 原有所有字段
  cus_id: req.user._id.toString(), // 新增用户ID作为自定义参数
};
  1. 修改成功回调路由,用req.body.cus_id获取用户ID:
router.post("/ssl-payment-success", async (req, res) => {
  try {
    const userId = req.body.cus_id;
    if (!userId) {
      return res.status(400).send('缺少用户信息');
    }
    const user = await User.findById(userId);
    if (!user) {
      return res.status(404).send('用户不存在');
    }
    user.wallet += Number(req.body.store_amount);
    await user.save(); // 必须加await,确保数据保存完成再跳转
    return res.redirect('/user/dashboard');
  } catch (err) {
    console.error(err);
    return res.status(500).send('支付结果处理失败');
  }
});

方法二:修复会话传递问题

如果一定要依赖会话,需要确保SSLCommerz的回调请求能携带会话Cookie,调整Session配置:

app.use(require('express-session')({
    secret: process.env.SESSION_SECRET,
    cookie: {
        expires: false,
        sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax', // 跨域场景设为none
        secure: process.env.NODE_ENV === 'production', // 生产环境必须开启HTTPS才能用none
        httpOnly: true // 安全设置,防止XSS
    },
    resave: false, // 优化设置,避免不必要的会话更新
    saveUninitialized: false // 只在需要时创建会话
}));

注意:sameSite: none必须配合secure: true,否则浏览器会拒绝Cookie,本地开发可以暂时设为lax或false。

方法三:添加身份验证中间件和异常捕获

给成功回调路由加上isLoggedIn中间件,同时用try-catch捕获异步异常:

router.post("/ssl-payment-success", isLoggedIn, async (req, res) => {
  try {
    const user = await User.findById(req.user._id);
    user.wallet += Number(req.body.store_amount);
    await user.save();
    return res.redirect('/user/dashboard');
  } catch (err) {
    console.error(err);
    return res.status(500).send('处理失败,请稍后重试');
  }
});

但这种方式下,如果回调请求没有会话,用户会被跳转到登录页,体验不如方法一。

内容的提问来源于stack exchange,提问作者Sifat Sikder

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 13:02:34