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

Flutter Web集成Stripe结账链接:支付后注册验证问题求助

解决方案:Flutter Web + Stripe结账链接 + Firebase Auth自动注册(无独立后端)

核心思路

由于支付流程在Stripe端完成,必须依赖Stripe Webhook确认支付状态,结合Firebase云函数替代独立后端处理支付验证与用户注册逻辑。前端仅负责触发支付、接收重定向后的会话ID,再通过云函数验证状态完成后续流程。


步骤1:用Firebase云函数生成Stripe结账链接

前端不集成Stripe SDK,通过调用云函数生成结账会话并返回跳转链接,避免密钥暴露在前端。

云函数代码(Node.js)

const functions = require("firebase-functions");
const stripe = require("stripe")(functions.config().stripe.secret_key);
const admin = require("firebase-admin");
admin.initializeApp();

exports.createCheckoutSession = functions.https.onCall(async (data, context) => {
  const { email } = data;

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ["card"],
    line_items: [
      {
        price: "你的Stripe价格ID", // 提前在Stripe后台创建商品/价格
        quantity: 1,
      },
    ],
    mode: "payment",
    success_url: `https://你的FlutterWeb域名.com/success?session_id={CHECKOUT_SESSION_ID}&email=${email}`,
    cancel_url: "https://你的FlutterWeb域名.com/cancel",
    metadata: { user_email: email }, // 存储用户邮箱,供后续Webhook使用
  });

  return { url: session.url };
});

Flutter Web前端调用代码

import 'package:firebase_functions/firebase_functions.dart';
import 'package:universal_html/html.dart' as html;

Future<void> startStripePayment(String userEmail) async {
  final functions = FirebaseFunctions.instance;
  try {
    final result = await functions.httpsCallable('createCheckoutSession').call({
      'email': userEmail,
    });
    final paymentUrl = result.data['url'] as String;
    // 跳转至Stripe结账页面
    html.window.open(paymentUrl, '_self');
  } catch (e) {
    print('生成支付链接失败: $e');
  }
}

步骤2:配置Stripe Webhook到Firebase云函数

在Stripe后台配置Webhook,监听checkout.session.completed事件,指向你的Firebase云函数URL(格式:https://us-central1-你的项目ID.cloudfunctions.net/stripeWebhook)。

Webhook处理云函数代码

exports.stripeWebhook = functions.https.onRequest(async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(
      req.rawBody,
      sig,
      functions.config().stripe.webhook_secret
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // 处理支付成功事件
  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;
    const userEmail = session.metadata.user_email;

    // 1. 在Firestore标记该邮箱已完成支付
    await admin.firestore().collection('paid_users').doc(userEmail).set({
      paid: true,
      sessionId: session.id,
      createdAt: admin.firestore.FieldValue.serverTimestamp(),
    });

    // 2. 自动创建Firebase Auth用户(生成随机密码,后续让用户重置)
    try {
      await admin.auth().createUser({
        email: userEmail,
        password: require('crypto').randomBytes(16).toString('hex'),
      });
    } catch (e) {
      // 忽略用户已存在的错误
      if (e.code !== 'auth/email-already-exists') {
        functions.logger.error('创建用户失败:', e);
      }
    }
  }

  res.json({ received: true });
});

步骤3:前端处理支付成功重定向

用户支付完成后,Stripe会重定向到你设置的success_url,前端从URL参数中获取会话ID和邮箱,调用云函数验证支付状态后完成自动登录/注册。

Flutter Web成功页面代码

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_functions/firebase_functions.dart';
import 'package:flutter/material.dart';
import 'package:universal_html/html.dart' as html;

class SuccessPage extends StatefulWidget {
  const SuccessPage({super.key});

  @override
  State<SuccessPage> createState() => _SuccessPageState();
}

class _SuccessPageState extends State<SuccessPage> {
  @override
  void initState() {
    super.initState();
    _verifyPaymentAndSignIn();
  }

  Future<void> _verifyPaymentAndSignIn() async {
    final uri = html.window.location.href;
    final urlParams = Uri.parse(uri).queryParameters;
    final sessionId = urlParams['session_id'];
    final email = urlParams['email'];

    if (sessionId == null || email == null) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('支付验证失败:参数缺失')),
      );
      return;
    }

    final functions = FirebaseFunctions.instance;
    try {
      // 调用云函数验证支付状态
      final result = await functions.httpsCallable('verifyPaymentSession').call({
        'session_id': sessionId,
        'email': email,
      });

      final isPaid = result.data['is_paid'] as bool;
      if (isPaid) {
        // 尝试登录,失败则发送密码重置邮件
        try {
          await FirebaseAuth.instance.signInWithEmailAndPassword(
            email: email,
            password: '这里无需真实密码,仅触发错误分支发送重置邮件',
          );
        } catch (e) {
          await FirebaseAuth.instance.sendPasswordResetEmail(email: email);
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(content: Text('支付成功!密码重置邮件已发送至$email')),
          );
        }
        // 跳转到应用首页
        Navigator.pushReplacementNamed(context, '/home');
      } else {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('支付未完成,请重试')),
        );
      }
    } catch (e) {
      print('验证支付失败: $e');
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('支付验证出错,请联系客服')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(child: CircularProgressIndicator()),
    );
  }
}

支付状态验证云函数代码

exports.verifyPaymentSession = functions.https.onCall(async (data, context) => {
  const { session_id, email } = data;

  // 1. 先查Firestore支付记录
  const paidUserDoc = await admin.firestore().collection('paid_users').doc(email).get();
  if (!paidUserDoc.exists || paidUserDoc.data()?.sessionId !== session_id) {
    return { is_paid: false };
  }

  // 2. 双重验证:调用Stripe API确认会话状态
  const session = await stripe.checkout.sessions.retrieve(session_id);
  return { is_paid: session.payment_status === 'paid' };
});

关键注意事项

  • Stripe密钥(secret key、webhook secret)需存储在Firebase云函数环境变量中:firebase functions:config:set stripe.secret_key="sk_xxx" stripe.webhook_secret="whsec_xxx"
  • 重定向URL必须添加到Stripe后台的「允许的重定向域名」列表中
  • 如果不需要自动创建用户,可在支付成功后让用户输入密码完成注册,只需从Firestore验证该邮箱已支付即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 22:50:10