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

Stripe结账流程获取customer_id遇错误:ReferenceError: session is not defined

如何在Stripe结账流程(Node.js/Mongoose)中获取customer_id并存入数据库

我为此问题耗时数周,查阅了多篇Stack Overflow相关问答,但现有方案都无法解决问题。

在用户注册后,我通过Stripe的create-checkout-session引导用户进入结账门户,希望获取Stripe生成的customer_id并存入数据库,以此监控订阅状态(作为入门/中级开发者,目前对Webhooks概念理解困难)。

创建结账会话的控制器代码

module.exports.createCheckoutSession = async (req, res) => {
    if (req.isAuthenticated()) {
        try {
            const session = await stripe.checkout.sessions.create({
                mode: "subscription",
                line_items: [
                    {
                        price: priceId,
                        quantity: 1,
                    },
                ],
                success_url: `${webUrl}/users/${req.user._id}/activations/?session_id={CHECKOUT_SESSION_ID}`,
                cancel_url: `${webUrl}/canceled.html`,
            });
            console.log("customer ID ", session.customer_id);
            console.log("query params ", req.query);
            console.log("req params ", req.params);
            console.log("session", session);
            return res.redirect(303, session.url);

        } catch (e) {
            res.status(400);
            return res.send({
                error: {
                    message: e.message,
                }
            });
        }
    }
    req.flash('error', "You must login before changing billing");
    res.render('/login');
}

激活页面控制器代码

module.exports.showActivations = async (req, res) => {
    // ...其他代码
    console.log("req params ", req.params);
    console.log("req headers ", req.headers)
    console.log("session ", session);
    res.render('users/activations', { activeEvents, qr, qrAgg });
    // ...其他代码
};

控制台输出情况

  • req.params仅包含用户注册时存入数据库的user_id(跳转至Stripe门户前的ID)
  • req.headers仅包含Cookie,无Stripe相关信息
  • 打印session时出现ReferenceError: session is not defined错误

解决方案

方法一:在成功页面通过Session ID查询获取Customer ID(临时方案)

你已经在success_url里带上了session_id={CHECKOUT_SESSION_ID},可以在showActivations函数中提取该ID,调用Stripe API查询完整会话信息来获取customer_id:

修改showActivations代码:

module.exports.showActivations = async (req, res) => {
    try {
        // 从查询参数中提取Stripe会话ID
        const sessionId = req.query.session_id;
        if (!sessionId) {
            throw new Error("Missing Stripe session ID");
        }

        // 调用Stripe API获取完整会话详情
        const stripeSession = await stripe.checkout.sessions.retrieve(sessionId);
        const customerId = stripeSession.customer; // 注意:Stripe API返回字段为`customer`而非`customer_id`

        // 将customerId存入Mongoose用户文档
        const user = await User.findById(req.params._id);
        user.stripeCustomerId = customerId;
        await user.save();

        // ...你的其他业务代码
        console.log("Stripe Customer ID:", customerId);
        res.render('users/activations', { activeEvents, qr, qrAgg, customerId });
    } catch (err) {
        console.error("Error retrieving Stripe session:", err);
        req.flash('error', "Failed to activate subscription");
        res.redirect('/some-error-page');
    }
};

注意事项

  • 该方案依赖前端跳转后的查询参数,存在被篡改的风险,仅适合快速验证,不建议作为长期方案。
  • 确保你的Stripe API密钥拥有查询Checkout Sessions的权限。

方法二:使用Webhooks获取Customer ID(推荐长期方案)

Webhooks是Stripe官方推荐的可靠方式,不受前端跳转影响,能确保你不会错过订阅完成事件,也是监控后续订阅状态(到期、付款失败等)的基础。

步骤1:配置Stripe Webhook端点

  1. 在Stripe Dashboard的【Webhooks】页面,添加一个接收端点(例如https://your-domain.com/stripe-webhook)。
  2. 选择监听事件:checkout.session.completed(用户成功完成结账时触发)。
  3. 复制Webhook签名密钥(用于验证请求来自Stripe)。

步骤2:实现Webhook处理函数

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

module.exports.handleStripeWebhook = async (req, res) => {
    const sig = req.headers['stripe-signature'];
    let event;

    try {
        // 验证Webhook请求签名,确保来自Stripe
        event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret);
    } catch (err) {
        console.error("Webhook signature verification failed:", err.message);
        return res.status(400).send(`Webhook Error: ${err.message}`);
    }

    // 处理结账完成事件
    if (event.type === 'checkout.session.completed') {
        const session = event.data.object;
        const customerId = session.customer;
        // 从metadata中提取你的用户ID(需提前存入)
        const userId = session.metadata.userId;

        if (userId && customerId) {
            // 将customerId更新到用户数据库
            await User.findByIdAndUpdate(userId, { stripeCustomerId: customerId });
            console.log(`Saved customer ID ${customerId} for user ${userId}`);
        }
    }

    // 返回200表示成功接收事件
    res.status(200).json({ received: true });
};

步骤3:修改Checkout Session创建代码,添加用户ID到metadata

在createCheckoutSession函数中,给Session添加metadata字段,关联你的用户ID:

const session = await stripe.checkout.sessions.create({
    mode: "subscription",
    line_items: [
        {
            price: priceId,
            quantity: 1,
        },
    ],
    success_url: `${webUrl}/users/${req.user._id}/activations/?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${webUrl}/canceled.html`,
    // 存入用户ID,用于Webhook中关联数据库用户
    metadata: {
        userId: req.user._id.toString()
    }
});

为什么推荐Webhooks?

  • 可靠性高:无论用户是否跳转回你的网站,Stripe都会发送通知,确保不会遗漏订阅事件。
  • 安全性强:通过签名验证过滤恶意请求。
  • 扩展性好:后续处理订阅到期、付款失败等事件,都可以复用Webhook机制。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 04:05:12