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
相关产品推荐
相关产品推荐

