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

NextJS中Stripe Webhooks返回307码及账单门户跳转问题

解决方案:采用客户端跳转,修正API路由返回逻辑

问题根源

  • 客户端跳转时出现307状态码,核心原因是你的/api/billing接口未返回200状态码携带账单门户URL,而是直接返回了307重定向响应。这种错误的响应不仅会干扰客户端跳转逻辑,还可能导致Stripe Webhook无法正常回调(Webhook依赖后端接口返回规范的状态码)。
  • 服务端跳转的CORS问题无解:浏览器的同源策略会拦截从你的域名发起请求后,服务端直接重定向到Stripe域名的操作,Access-Control-Allow-Origin头只针对AJAX请求,不适用于页面级跨域跳转,因此这条路走不通。

具体修复步骤

  1. 修正后端API路由
    确保/api/billing接口创建Stripe账单门户会话后,返回200状态码+会话URL,而非直接重定向。示例代码(NextJS App Router):

    import { NextResponse } from 'next/server';
    import Stripe from 'stripe';
    
    const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
      apiVersion: '2024-06-20', // 替换为你当前使用的Stripe API版本
    });
    
    // 需自行实现:根据租户ID查询关联的Stripe客户ID
    async function getCustomerIdByTenantId(tenantId: string) {
      // 从数据库或存储介质中获取对应关系
      return 'cus_xxx';
    }
    
    export async function POST(request: Request) {
      try {
        const { tenantId } = await request.json();
        const customerId = await getCustomerIdByTenantId(tenantId);
    
        const portalSession = await stripe.billingPortal.sessions.create({
          customer: customerId,
          return_url: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`, // 用户操作完成后返回的页面
        });
    
        // 返回200状态码和账单门户URL
        return NextResponse.json(portalSession.url, { status: 200 });
      } catch (error) {
        return NextResponse.json({ error: (error as Error).message }, { status: 500 });
      }
    }
    
  2. 保留客户端跳转逻辑
    原客户端代码无需大幅修改,只需确保API返回纯URL字符串,再通过路由跳转即可:

    const createBillingPortal = async (tenantId: string) => {
      const body = { tenantId };
      const { data } = await axios.post(Routes.API.BILLING, body);
      return data;
    };
    
    export const useCreateBillingPortal = (
      tenantId: string,
      router: NextRouter,
    ) => {
      const mutation = useMutation({
        mutationFn: () => createBillingPortal(tenantId),
        onSuccess: (portalUrl) => {
          router.push(portalUrl);
          // 也可以用window.location.href = portalUrl,效果一致
        },
        onError: (error) => {
          console.error('创建账单门户失败:', error);
          // 这里可添加用户友好的错误提示
        }
      });
      return mutation;
    };
    

为什么这样能解决问题?

  • API接口返回规范的200状态码,符合HTTP协议要求,不会干扰Stripe Webhook的正常回调(Webhook是Stripe直接调用你的后端接口,与客户端跳转完全独立)。
  • 客户端跳转是浏览器主动发起的页面跳转,跨域到Stripe域名属于合法操作(页面跳转不受同源策略限制),不会触发CORS错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 14:25:29