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

Next.js 14 + Vercel部署后Stripe订阅取消功能报错求助

问题描述

Next.js 14项目本地运行正常,部署到Vercel后,点击「Cancel your subscription」按钮触发Stripe的manageBilling Server Action时,控制台报错:

"Error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details..."

项目中其他Stripe功能(如结账)可正常运行。

相关代码

utils/actions/stripe.ts(Stripe操作函数)

"use server";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SK!);

export default async function checkout(email:string, priceId:string, redirectTo:string) {
    return JSON.stringify(
        await stripe.checkout.sessions.create({
        success_url:redirectTo || process.env.SITE_URL,
        cancel_url: process.env.SITE_URL,
        customer_email: email,
        line_items:[{price:priceId, quantity:1}],
        mode: 'subscription'
    })
)
}

export async function manageBilling(customer_id:string){
    return JSON.stringify(await stripe.billingPortal.sessions.create({
        customer: customer_id,
        return_url: process.env.SITE_URL,
    }))
}

ProfileCard.tsx(调用manageBilling的客户端组件)

import useUser from "@/app/hook/useUser";
import { manageBilling } from "@/utils/actions/stripe";

export default function ProfileCard() {
    const{data:user, isLoading} = useUser();
    if(isLoading) return <></>


    const handleBilling = async () => {
        try {
            if (user?.subscriptions?.customer_id) {
                const data = JSON.parse(await manageBilling(user?.subscriptions?.customer_id));
                window.location.href = data.url;
            }
        } catch (error) {
            console.error("Billing error:", error);
            alert("An error occurred while managing your billing. Please try again later.");
        }
    };

    return(
     <div className="flex items-start card bg-base-100 border border-gray-600 shadow-2xl h-[350px] w-[500px] sm:max-md:w-[350px]">
        <p className="text-2xl font-semibold ml-48 sm:max-md:ml-32 mt-3 italic underline">User info</p>
        <div className="card-body mt-3">
            <div className="space-y-3">
                <p className="text-xl underline">Email: {user?.email}</p>
                {user?.subscriptions?.end_at? 
                    <>
                    <p className="text-xl font-semibold text-green-600">
                        User type: Active
                    </p>
                    <p className="text-lg">
                    Subscription end: {""}
                    {new Date(user?.subscriptions?.end_at).toDateString()}
                </p>
                </>
                :
                <>
                <p className="text-xl font-semibold text-gray-300">
                    User type: Inactive
                </p>
                <p className="text-lg">
                    Subscription end: Inactive
                </p>
                </>
                }
                <p className="text-lg">
                    Created at: {new Date(user?.created_at!).toDateString()}
                </p>
            </div>
            {user?.subscriptions?.customer_id &&
                <div className="mt-8 flex items-center justify-center ml-32"> 
                <button className="btn btn-error rounded-md" onClick={handleBilling}>Cancel your subscription</button>
                </div>
                }
         </div>
        </div>
    );
}

排查原因

  • 环境变量配置问题:Vercel上可能未正确设置STRIPE_SK或SITE_URL,尤其是SITE_URL在生产环境需要完整的Vercel域名,而非本地的localhost地址。
  • Stripe API权限或无效客户ID:生产环境的Stripe密钥可能缺少创建Billing Portal会话的权限,或者传入的customer_id在Stripe生产环境中不存在。
  • Server Action无错误捕获:manageBilling函数未捕获Stripe API调用异常,错误直接抛出后被Next.js在生产环境屏蔽细节。
  • Vercel函数日志未利用:生产环境的具体错误信息会记录在Vercel函数日志中,未查看则无法定位问题。

解决办法

  1. 检查并修正Vercel环境变量
    登录Vercel控制台,进入项目Settings > Environment Variables:

    • 确认STRIPE_SK使用Stripe生产环境密钥(以sk_live_开头)
    • 确认SITE_URL设置为Vercel部署的完整域名(如https://your-project.vercel.app)
      保存后重新部署项目,确保环境变量生效。
  2. 为Server Action添加错误捕获
    修改manageBilling函数,捕获异常并返回结构化结果:

    export async function manageBilling(customer_id:string){
        try {
            const session = await stripe.billingPortal.sessions.create({
                customer: customer_id,
                return_url: process.env.SITE_URL!,
            });
            return JSON.stringify({ success: true, data: session });
        } catch (error) {
            console.error("Stripe Billing Portal Error:", error);
            return JSON.stringify({ success: false, error: (error as Error).message });
        }
    }
    

    同时更新客户端调用逻辑,处理错误信息:

    const handleBilling = async () => {
        try {
            if (user?.subscriptions?.customer_id) {
                const response = JSON.parse(await manageBilling(user.subscriptions.customer_id));
                if (response.success) {
                    window.location.href = response.data.url;
                } else {
                    throw new Error(response.error);
                }
            }
        } catch (error) {
            console.error("Billing error:", error);
            alert(`An error occurred: ${(error as Error).message || "Please try again later."}`);
        }
    };
    
  3. 验证Stripe客户ID与密钥权限

    • 在Stripe Dashboard的Customers页面,确认传入的customer_id是生产环境中存在的有效ID。
    • 检查Stripe生产密钥权限,默认Secret Key拥有全部权限,若使用受限密钥需确保包含billing_portal.sessions.create权限。
  4. 查看Vercel函数日志
    进入Vercel项目的Functions页面,找到对应Server Action的函数日志,查看具体错误详情(生产环境下Next.js屏蔽的细节会在此处显示)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 15:30:21