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

Firebase函数无法执行全部步骤的技术求助

Firebase函数触发后返回200但未执行Set操作的问题排查与修复

核心问题诊断

你的代码存在Promise链断裂和异步操作未正确串联的问题,这是导致函数提前返回200但未执行后续set操作的主要原因,次要原因可能包含Stripe对象结构验证缺失:

1. Promise链断裂(最关键)

原代码中多个then块内的异步操作(比如database.collection().set()、axios.request())没有返回Promise,导致Promise链直接跳过这些异步操作,进入下一个then块,最终提前返回200响应,而异步操作还没执行完成甚至没开始。

2. Plan变量赋值逻辑无效

在查询plans集合的forEach中,return plan = doc.data()无法将值传递到外层Promise链,因为forEach的返回值不会影响外层的then回调,导致后续then块中plan可能为undefined,执行set操作时静默失败(错误未被捕获)。

3. 错误捕获不完整

内部异步操作的错误无法冒泡到外层catch块,因为未返回Promise,错误被静默吞掉,无法触发错误响应,只能返回成功的200。

4. Stripe对象就绪性验证缺失

没有添加日志验证Stripe传递的subscription对象结构是否符合预期,比如subscription.items.data[0].price是否存在,若Stripe对象未就绪,会直接导致后续操作失败但无报错。


修复方案

1. 改用Async/Await简化异步逻辑(推荐)

Async/Await比Promise链式调用更易维护,能避免嵌套和链断裂问题:

const functions = require("firebase-functions");
const axios = require("axios");

class stripeWebhookSubscription {
  static async createSubscription(database, subscription, res) {
    try {
      // 1. 创建订阅记录
      const subscriptionDoc = await database.collection("subscriptions").add({
        id: subscription.id,
        customer: subscription.customer,
        status: subscription.status,
        price: {
          amount: (subscription.items.data[0].price.unit_amount / 100).toFixed(2),
          interval: subscription.items.data[0].price.recurring.interval,
          interval_count: subscription.items.data[0].price.recurring.interval_count,
        },
        product: subscription.items.data[0].price.product,
        created: subscription.created,
        current_period_start: subscription.current_period_start,
        current_period_end: subscription.current_period_end,
        cancel_at: subscription.cancel_at,
        cancel_at_period_end: subscription.cancel_at_period_end,
        payment_gateway: "stripe",
        current_usage: 1,
      });
      const barcode = subscriptionDoc.id;
      console.log("订阅记录创建成功,ID:", barcode);

      // 2. 查询对应的Plan
      const productId = subscription.items.data[0].price.product;
      console.log("查询Plan对应的Stripe产品ID:", productId);
      const planQuery = await database.collection("plans").where("stripe.product", "==", productId).limit(1).get();
      if (planQuery.empty) {
        throw new Error(`未找到对应产品ID的Plan:${productId}`);
      }
      const plan = planQuery.docs[0].data();
      console.log("找到Plan:", plan);

      // 3. 更新Customer记录
      await database.collection("customers").doc(subscription.customer).set({
        merchant: plan.merchant,
        subscriptions: [barcode],
      }, {merge: true});
      console.log("Customer更新完成:", subscription.customer);

      // 4. 更新Subscription记录的merchant字段
      await database.collection("subscriptions").doc(barcode).set({
        merchant: plan.merchant,
      }, {merge: true});
      console.log("Subscription merchant字段更新完成:", barcode);

      // 5. 发送确认邮件
      const customerDoc = await database.collection("customers").doc(subscription.customer).get();
      const customer = customerDoc.data();
      await axios.request({
        url: "https://api.sendinblue.com/v3/smtp/email",
        method: "post",
        headers: {
          "api-key": functions.config().sendinblue.key,
          "Content-Type": "application/json",
        },
        data: {
          "to": [
            {
              "email": customer.email,
              "name": customer.name,
            },
          ],
          "replyTo": {
            "email": "support@scanable.com.au",
            "name": "Scanable",
          },
          "templateId": 2,
          "params": {
            "NAME": customer.name,
            "SUBSCRIPTION": barcode,
          },
        },
      });
      console.log("确认邮件发送成功:", customer.email);

      // 6. 返回成功响应
      return res.status(200).send(`✅ Subscription ${subscription.id} created!`);
    } catch (err) {
      console.error("创建订阅失败:", err);
      return res.status(400).send(`⚠️ Error creating subscription (${subscription.id}): ${err.message}`);
    }
  }
}

2. 额外调试与验证步骤

  • 添加Stripe对象验证:在函数开头添加日志,打印subscription的完整结构,确认items.data[0].price等字段是否存在:
    console.log("Stripe Subscription对象:", JSON.stringify(subscription, null, 2));
    
  • 验证Stripe Webhook签名:确保你在Firebase函数中验证了Stripe Webhook的签名,避免接收无效请求。
  • 检查Firebase权限:确认Firebase函数的服务账号拥有Firestore的读写权限。
  • 查看Firebase函数日志:通过Firebase控制台查看函数的日志输出,确认每一步是否执行,是否有错误信息。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 10:18:31