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

如何在NextJS中借助Firebase扩展生成Stripe账单门户链接?

解决方案:生成Stripe账单门户链接(NextJS + Firebase Stripe扩展)

核心原则

  • Stripe账单门户链接必须后端生成,绝对不能在前端使用Stripe Secret Key,否则会泄露密钥引发安全风险。
  • Firebase Stripe扩展会自动将用户的Stripe Customer ID存储在Firestore中(默认路径:users/{userId}/stripeCustomerId),需要先获取该ID才能生成链接。

步骤1:创建NextJS API路由(后端)

在pages/api/create-billing-portal.ts(或.js)中编写后端逻辑,调用Stripe API生成账单门户链接:

import Stripe from 'stripe';
import admin from 'firebase-admin';
import { getApps } from 'firebase-admin/app';

// 初始化Firebase Admin(仅初始化一次)
if (!getApps().length) {
  admin.initializeApp({
    credential: admin.credential.cert(JSON.parse(process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT || '')),
  });
}

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-06-20', // 使用Stripe最新API版本
});

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: '仅支持POST请求' });
  }

  try {
    // 验证用户身份:从请求头获取Firebase ID Token
    const idToken = req.headers.authorization?.split('Bearer ')[1];
    if (!idToken) {
      return res.status(401).json({ error: '未授权访问' });
    }

    // 验证Token并获取用户UID
    const decodedToken = await admin.auth().verifyIdToken(idToken);
    const userId = decodedToken.uid;

    // 从Firestore获取用户的Stripe Customer ID
    const userDoc = await admin.firestore().collection('users').doc(userId).get();
    const stripeCustomerId = userDoc.data()?.stripeCustomerId;

    if (!stripeCustomerId) {
      return res.status(400).json({ error: '未找到用户对应的Stripe客户记录' });
    }

    // 创建账单门户会话
    const session = await stripe.billingPortal.sessions.create({
      customer: stripeCustomerId,
      return_url: `${process.env.NEXT_PUBLIC_APP_URL}/account`, // 用户操作完成后返回的页面
    });

    return res.status(200).json({ url: session.url });
  } catch (error) {
    console.error('生成账单门户链接失败:', error);
    return res.status(500).json({ error: '生成支付管理链接失败,请稍后重试' });
  }
}

步骤2:前端调用API并跳转

在用户设置页面(如pages/account/settings.tsx)中,添加按钮触发API调用,获取链接后跳转至Stripe账单门户:

import { useState } from 'react';
import { getAuth, getIdToken } from 'firebase/auth';

export default function SettingsPage() {
  const [loading, setLoading] = useState(false);

  const handleEditPayment = async () => {
    setLoading(true);
    try {
      // 获取当前登录用户的Firebase ID Token
      const auth = getAuth();
      const user = auth.currentUser;
      if (!user) {
        alert('请先登录');
        return;
      }
      const idToken = await user.getIdToken();

      // 调用自建API路由
      const response = await fetch('/api/create-billing-portal', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${idToken}`,
        },
      });

      if (!response.ok) throw new Error('请求失败');

      const data = await response.json();
      // 跳转至Stripe账单门户
      window.location.href = data.url;
    } catch (error) {
      console.error('错误:', error);
      alert('生成支付管理链接失败,请稍后重试');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <button
        onClick={handleEditPayment}
        disabled={loading}
      >
        {loading ? '加载中...' : '编辑支付信息'}
      </button>
    </div>
  );
}

过往尝试的错误说明

  1. 方法2错误原因:stripe-js是前端专用库,仅支持支付元素渲染、支付确认等前端操作,不支持创建账单门户会话,该操作必须由后端完成。
  2. 方法3错误原因:stripe后端库不能在前端使用,一是会泄露Stripe Secret Key,二是浏览器环境不兼容Node.js API,导致请求无响应。
  3. 方法5错误原因:
    • 前端直接调用Stripe API会暴露Secret Key,属于严重安全漏洞,绝对禁止。
    • 请求头同时设置Content-Type: application/json和application/x-www-form-urlencoded,参数格式冲突;且body未正确传入customer参数,导致报错。

内容的提问来源于stack exchange,提问作者engineer-x

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 22:05:22