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

Stripe结账流程后req.user与req.params缺失,无法存储customerId

Stripe分层定价订阅集成问题:无法将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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 22:35:20