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

集成Stripe Payment Sheet(Firebase+云函数)时遇JSON解析错误

问题排查:Flutter + Firebase云函数 + Stripe支付报错:Unexpected Character...

我在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 02:30:43