Stripe结账流程后req.user与req.params缺失,无法存储customerId
我正在尝试在应用中集成Stripe分层定价订阅计费功能,目前需要完成两项核心工作:
- 允许新用户通过集成创建Stripe客户账户
- 监控Stripe webhook事件,在客户订阅有效时提供对应访问权限
用户流程如下:
- 在应用内创建个人资料并保存到数据库 → 重定向至Stripe结账门户填写账单信息(信息存入Stripe数据库)→ 尝试将Stripe的customerId保存到自己的数据库,用于后续监控订阅状态
但现在卡在了customerId的存储环节:用户从Stripe结账门户返回应用后,req.user和req.params都为空,没法定位到数据库里的用户。
控制器函数
module.exports.stripeWebhook = async (req, res) => { let data; const webhookSecret = stripeWebhookSecret; if (webhookSecret) { let event; let signature = req.headers["stripe-signature"]; try { event = stripe.webhooks.constructEvent( req.body, signature, webhookSecret ); } catch (err) { console.log(`⚠️ Webhook signature verification failed.`); return res.sendStatus(400); } data = event.data; eventType = event.type; } else { // retrieve the event data directly from the request body. data = req.body.data; eventType = req.body.type; } switch (eventType) { case 'payment_intent.succeeded': { console.log('PaymentIntent was successful!'); break; } case 'checkout.session.completed': // Payment is successful and the subscription is created. // You should provision the subscription and save the customer ID to your database. console.log(data.object.customer); // 此处能正常输出 const user = await User.findById(req.user.id); // 此处返回空,导致后续代码无法执行 user.stripeId.push(data.object.customer); await user.save(); break; default: } res.sendStatus(200); };
App.js
app.use(bodyParser.raw({type: "application/json"})); app.use(express.json({ limit: '1mb' })); app.use(express.urlencoded({ extended: true }));
我附上App.js代码是因为bodyparser.raw会影响控制器函数中请求体的解析方式。我原本依赖req.user或req.params查找数据库中的用户,但无法生效,请问如何按照Stripe注释的要求将customerId保存到数据库?
解决方案
首先明确:Stripe的webhook请求是直接从Stripe服务器发起的,并非用户浏览器的请求,所以不会携带你的应用的用户会话信息,req.user自然为空,不能用这个方式定位用户。正确的做法是在创建Checkout Session时把用户ID传递给Stripe,再通过webhook事件带回。
1. 创建Checkout Session时传入用户元数据
在你生成Stripe Checkout Session的代码里,添加metadata字段,把当前登录用户的ID传进去,让Stripe绑定这个信息:
const session = await stripe.checkout.sessions.create({ payment_method_types: ['card'], line_items: [/* 替换成你的定价配置 */], mode: 'subscription', success_url: 'https://your-app.com/success', // 替换成你的成功跳转地址 cancel_url: 'https://your-app.com/cancel', // 替换成你的取消跳转地址 metadata: { user_id: req.user.id // 这里传入当前登录用户的ID } });
2. 在webhook中通过元数据获取用户ID并保存customerId
修改checkout.session.completed事件的处理逻辑,从事件对象的metadata里取出用户ID,再去数据库找到用户并保存customerId:
case 'checkout.session.completed': const customerId = data.object.customer; const userId = data.object.metadata.user_id; // 从Stripe带回的元数据中取用户ID const user = await User.findById(userId); if (user) { // 通常一个用户对应一个Stripe customerId,直接赋值即可,不需要push(除非你的业务允许多个) user.stripeId = customerId; await user.save(); } break;
3. 调整body解析中间件顺序
你的App.js里同时使用了bodyParser.raw和express.json,Stripe webhook要求请求体是raw格式的JSON,所以要确保webhook的路由优先使用raw解析,避免中间件冲突:
// 先单独给webhook路由配置raw解析 app.post('/stripe-webhook', bodyParser.raw({type: "application/json"}), stripeWebhookController); // 再给其他路由配置json和urlencoded解析 app.use(express.json({ limit: '1mb' })); app.use(express.urlencoded({ extended: true }));
这样就能正确把Stripe的customerId关联到你数据库里的用户了。
内容的提问来源于stack exchange,提问作者econobro

