集成Stripe Payment Sheet(Firebase+云函数)时遇JSON解析错误
我在Flutter应用中通过Firebase的Stripe扩展结合JavaScript Firebase云函数实现Stripe支付功能,推测问题出在服务端创建客户及支付意图环节,当前遇到报错:"Unexpected Character (at line 2, character 1) "。
服务端云函数代码
const functions = require("firebase-functions"); const stripe = require("stripe")("my test secret key"); // 其他Stripe功能可正常使用 exports.stripePaymentIntentRequest = functions.https.onRequest( async (req, res) => { const {email, amount} = req.body; try { let customerId; // 获取匹配邮箱的客户 const customerList = await stripe.customers.list({ email: email, limit: 1, }); // 检查客户是否存在,不存在则创建新客户 if (customerList.data.length !== 0) { customerId = customerList.data[0].id; } else { const customer = await stripe.customers.create({ email: email, }); customerId = customer.data.id; } // 创建关联客户的临时密钥 const ephemeralKey = await stripe.ephemeralKeys.create( {customer: customerId}, {apiVersion: "2022-11-15"}, ); // 创建支付意图 const paymentIntent = await stripe.paymentIntents.create({ amount: parseInt(amount), currency: "gbp", customer: customerId, }); res.status(200).send({ paymentIntent: paymentIntent.client_secret, ephemeralKey: ephemeralKey.secret, customer: customerId, success: true, }); } catch (error) { res.status(404).send({success: false, error: error.message}); } });
客户端Flutter代码
try { // 1. 在服务端创建支付意图 final response = await http.post( Uri.parse( 'https://us-central1-clublink-1.cloudfunctions.net/stripePaymentIntentRequest'), headers: {"Content-Type": "application/json"}, body: json.encode({ 'email': email, 'amount': amount.toString(), }), ); final jsonResponse = json.decode(response.body); if (jsonResponse['error'] != null) { throw Exception(jsonResponse['error']); } log(jsonResponse.toString()); //2. 初始化支付表单 await Stripe.instance.initPaymentSheet( paymentSheetParameters: SetupPaymentSheetParameters( paymentIntentClientSecret: jsonResponse['paymentIntent'], merchantDisplayName: 'Clublink UK', customerId: jsonResponse['customer'], customerEphemeralKeySecret: jsonResponse['ephemeralKey'], style: ThemeMode.dark, ), ); await Stripe.instance.presentPaymentSheet(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Payment completed!')), ); } catch (e) { if (e is StripeException) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error from Stripe: ${e.error.localizedMessage}'), ), ); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error: $e')), ); } }
排查方向及修复方案
这个报错说明客户端收到了HTML响应而非预期的JSON,通常是云函数执行出错,返回了Firebase的错误页面。以下是具体排查点和修复建议:
修正客户创建后的ID获取逻辑:
服务端代码中创建新客户时,customerId = customer.data.id是错误的。stripe.customers.create返回的直接是客户对象,没有data属性,应改为customerId = customer.id。这个错误会直接抛出异常,导致函数返回错误。添加CORS配置:
跨域请求会触发OPTIONS预检请求,若函数未处理会导致请求失败。在函数开头添加CORS处理逻辑:res.set('Access-Control-Allow-Origin', '*'); if (req.method === 'OPTIONS') { res.set('Access-Control-Allow-Methods', 'POST'); res.set('Access-Control-Allow-Headers', 'Content-Type'); res.set('Access-Control-Max-Age', '3600'); res.status(204).send(''); return; }验证请求参数有效性:
对客户端传递的amount做校验,避免非数字值导致Stripe报错:const amountInt = parseInt(amount, 10); if (isNaN(amountInt) || amountInt <= 0) { throw new Error('Invalid amount value'); }使用
res.json()替代res.send():res.json()会自动设置正确的Content-Type头,确保返回标准JSON格式,避免解析错误。检查函数部署与URL有效性:
直接用Postman或浏览器发送POST请求到函数URL,传入{"email": "test@example.com", "amount": "100"},查看返回结果是JSON还是HTML。若返回HTML,说明函数部署失败或存在配置问题,需查看Firebase控制台的函数日志。添加错误日志:
在catch块中添加console.error('Error:', error),方便在Firebase控制台查看具体错误信息,定位问题根源。
修复后的服务端代码示例
const functions = require("firebase-functions"); const stripe = require("stripe")("my test secret key"); exports.stripePaymentIntentRequest = functions.https.onRequest( async (req, res) => { // 处理CORS预检请求 res.set('Access-Control-Allow-Origin', '*'); if (req.method === 'OPTIONS') { res.set('Access-Control-Allow-Methods', 'POST'); res.set('Access-Control-Allow-Headers', 'Content-Type'); res.set('Access-Control-Max-Age', '3600'); res.status(204).send(''); return; } const {email, amount} = req.body; try { // 参数校验 if (!email || !amount) { throw new Error('Missing email or amount'); } const amountInt = parseInt(amount, 10); if (isNaN(amountInt) || amountInt <= 0) { throw new Error('Invalid amount value'); } let customerId; // 获取匹配邮箱的客户 const customerList = await stripe.customers.list({ email: email, limit: 1, }); // 检查客户是否存在,不存在则创建 if (customerList.data.length !== 0) { customerId = customerList.data[0].id; } else { const customer = await stripe.customers.create({ email: email, }); customerId = customer.id; // 修正ID获取逻辑 } // 创建临时密钥 const ephemeralKey = await stripe.ephemeralKeys.create( {customer: customerId}, {apiVersion: "2022-11-15"}, ); // 创建支付意图 const paymentIntent = await stripe.paymentIntents.create({ amount: amountInt, currency: "gbp", customer: customerId, }); // 返回标准JSON响应 res.status(200).json({ paymentIntent: paymentIntent.client_secret, ephemeralKey: ephemeralKey.secret, customer: customerId, success: true, }); } catch (error) { console.error('Payment intent error:', error); // 记录错误日志 res.status(400).json({success: false, error: error.message}); // 使用400状态码表示客户端错误 } });
内容的提问来源于stack exchange,提问作者Seonaidh Morrison

