集成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,回调时从请求参数里获取,不依赖会话:
- 修改支付创建代码,添加用户ID字段:
const data = { // ... 原有所有字段 cus_id: req.user._id.toString(), // 新增用户ID作为自定义参数 };
- 修改成功回调路由,用
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
相关产品推荐
相关产品推荐

