如何将Stripe会话与Node.js+Express应用会话关联并传递客户邮箱
解决方案:从Stripe Webhook传递客户邮箱到成功页
问题背景
基于Node.js+Express搭建的Web应用,核心流程是:
- 首页通过静态Stripe Payment Link引导用户跳转至结账页面
- 支付完成后重定向至
/success页,需显示付款客户的邮箱 - Webhook可捕获客户邮箱,但因Stripe的Webhook请求与用户会话不共享,无法通过
req.session传递数据
可行解决方案
方案一:动态生成Checkout链接关联用户会话(推荐)
通过Stripe API动态生成Checkout Session,将用户的sessionID作为关联ID传入,Webhook拿到事件后找到对应会话存储邮箱,成功页即可读取。
代码修改步骤:
- 替换静态Payment Link,后端生成Checkout URL
// 替换原GET /路由 app.get('/', async (req, res) => { console.log('Entering default route....session id = ' + req.sessionID); // 创建Stripe Checkout Session,传入用户sessionID作为关联标识 const checkoutSession = await stripe.checkout.sessions.create({ payment_link: 'pl_test_xxxx', // 替换为你的Payment Link ID client_reference_id: req.sessionID, success_url: 'http://localhost:3000/success', cancel_url: 'http://localhost:3000/', }); // 渲染首页模板,传递生成的结账链接 res.render('index', { checkoutUrl: checkoutSession.url }); });
对应的index.ejs模板:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Stripe Payment</title> </head> <body> <p>Stripe Payment</p> <a href="<%= checkoutUrl %>">buy</a> </body> </html>
- 修改Webhook事件处理,关联会话存储邮箱
app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => { console.log('Entering Stripe webhook....') const sig = req.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret); } catch (err) { console.log('Webhook Error: ' + err.message); return res.status(400).send(`Webhook Error: ${err.message}`); } switch (event.type) { case 'checkout.session.completed': const sessionData = event.data.object; const customerEmail = sessionData.customer_details.email; const userSessionId = sessionData.client_reference_id; if (userSessionId && customerEmail) { // 从会话存储中获取用户会话并保存邮箱 app.sessionStore.get(userSessionId, (err, userSession) => { if (err) { console.error('Failed to fetch session:', err); return; } if (userSession) { userSession.customerEmail = customerEmail; app.sessionStore.set(userSessionId, userSession, (saveErr) => { saveErr ? console.error('Failed to save session:', saveErr) : console.log('Email saved to session'); }); } }); } break; default: console.log(`Unhandled event type ${event.type}`); } res.send(); });
此时/success路由无需修改,即可从req.session.customerEmail读取邮箱并渲染。
方案二:临时存储+查询参数(快速适配静态Payment Link)
若不想修改Payment Link生成方式,可通过唯一订单ID关联邮箱,用临时存储(如Redis、内存对象)暂存数据。
代码修改步骤:
- 安装依赖并生成订单ID
npm install uuid
const { v4: uuidv4 } = require('uuid'); // 临时存储(生产环境替换为Redis/数据库) const orderEmailMap = {}; // 修改首页路由 app.get('/', (req, res) => { const orderId = uuidv4(); req.session.orderId = orderId; // 构造带订单ID的支付链接(需确保Stripe允许自定义success_url参数) const paymentUrl = `https://buy.stripe.com/test_28oaGrdLl3q28jCbII?success_url=http://localhost:3000/success?orderId=${orderId}`; res.render('index', { paymentUrl }); });
- Webhook中存储邮箱与订单ID的关联
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { // 签名验证代码省略... switch (event.type) { case 'checkout.session.completed': const sessionData = event.data.object; const customerEmail = sessionData.customer_details.email; // 从success_url中解析订单ID const urlParams = new URLSearchParams(new URL(sessionData.success_url).search); const orderId = urlParams.get('orderId'); if (orderId && customerEmail) { orderEmailMap[orderId] = customerEmail; // 设置1小时过期,避免内存泄漏 setTimeout(() => delete orderEmailMap[orderId], 3600000); } break; default: console.log(`Unhandled event type ${event.type}`); } res.send(); });
- 修改成功页路由读取邮箱
app.get('/success', (req, res) => { const orderId = req.query.orderId; const customerEmail = orderEmailMap[orderId] || '未获取到邮箱'; res.render('success', { customerEmail }); });
注意事项
- 内存存储仅适合开发测试,生产环境必须使用Redis、数据库等持久化存储
express-session默认内存存储不可用于生产,推荐搭配connect-redis等插件- 务必保证Stripe Webhook签名验证正确,防止恶意请求
内容的提问来源于stack exchange,提问作者marius
相关产品推荐
相关产品推荐

