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

Stripe支付成功/取消时Webhook未创建Firestore文档问题

Stripe Webhook未触发Firestore文档创建的问题排查与修复

核心问题分析

  1. Webhook端点与函数名不匹配:你在Stripe控制台配置的Webhook指向handleStripePayments,但代码中导出的是handleStripePaymentSuccess和handleStripePaymentFailure两个独立函数,导致事件无法正确路由。
  2. 缺少Webhook签名验证:未验证Stripe请求的签名,可能导致请求被拒绝或处理无效数据。
  3. Firestore初始化冗余:同时使用firebase-admin和@google-cloud/firestore初始化数据库,可能引发冲突。
  4. 事件处理逻辑漏洞:handleStripePaymentSuccess中调用functions.handleStripePaymentFailure属于错误调用,functions对象无此方法,会直接抛出异常。

修复步骤与代码优化

1. 合并Webhook处理函数

将两个事件的处理逻辑合并到一个统一的函数中,确保Stripe的Webhook请求能正确触发。

2. 添加Stripe签名验证

必须验证Stripe请求的签名,防止恶意请求。在Stripe控制台的Webhook端点设置中获取Signing secret,填入代码对应位置。

3. 简化Firestore初始化

直接使用admin.firestore()初始化数据库,避免冗余依赖冲突。

4. 完善事件分支与错误处理

添加空值判断,避免因缺少邮箱信息导致的崩溃;统一事件处理流程,减少逻辑漏洞。


修复后的完整代码

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const stripe = require('stripe');

admin.initializeApp();
const db = admin.firestore();
// 替换为你的Stripe测试模式Secret Key
const stripeClient = new stripe('你的Stripe Secret Key');
// 替换为你的Stripe Webhook测试模式Signing Secret
const stripeWebhookSecret = '你的Stripe Webhook签名密钥';

exports.handleStripeWebhook = functions.https.onRequest(async (req, res) => {
  let event;

  // 验证Stripe Webhook签名
  try {
    event = stripeClient.webhooks.constructEvent(
      req.rawBody,
      req.headers['stripe-signature'],
      stripeWebhookSecret
    );
  } catch (error) {
    functions.logger.error('Webhook签名验证失败:', error.message);
    return res.status(400).send(`Webhook Error: ${error.message}`);
  }

  try {
    let customerEmail;
    let message;

    switch (event.type) {
      case 'payment_intent.succeeded': {
        const paymentIntent = event.data.object;
        customerEmail = paymentIntent.charges.data[0].billing_details.email;
        const username = extractUsernameFromEmail(customerEmail);
        
        const html = `你的HTML邮件内容`;
        message = {
          subject: '恭喜你成功购买求职工具包!',
          text: `亲爱的${username},`,
          html: html,
        };
        break;
      }
      case 'payment_intent.canceled': {
        const paymentIntent = event.data.object;
        // 增加空值判断,避免无邮箱时崩溃
        customerEmail = paymentIntent.charges.data[0]?.billing_details.email;
        if (!customerEmail) {
          functions.logger.warn('取消订单未找到用户邮箱,跳过邮件发送');
          return res.status(200).send('无有效邮箱信息');
        }
        const username = extractUsernameFromEmail(customerEmail);
        
        const html = `你的HTML邮件内容`;
        message = {
          subject: '请完成你的求职工具包购买!',
          text: `亲爱的${username},`,
          html: html,
        };
        break;
      }
      default:
        functions.logger.info(`未处理事件类型: ${event.type}`);
        return res.status(200).send(`未处理事件: ${event.type}`);
    }

    // 确认邮箱和消息存在时再创建文档
    if (customerEmail && message) {
      await db.collection('mail').add({
        to: customerEmail,
        message: message,
      });
      res.status(200).send('邮件文档已成功创建');
    } else {
      res.status(200).send('无需创建邮件文档');
    }
  } catch (error) {
    functions.logger.error('处理Stripe事件出错:', error);
    res.status(500).send('内部服务器错误');
  }
});

function extractUsernameFromEmail(email) {
  const atIndex = email.indexOf('@');
  return atIndex !== -1 ? email.substring(0, atIndex) : email;
}

额外排查步骤

  • 查看Firebase Functions日志:在Firebase控制台→Functions→日志,检查是否有签名验证失败、数据库操作错误等日志信息。
  • 检查Stripe Webhook交付状态:在Stripe控制台→Webhooks→对应端点→事件列表,查看事件的交付状态,若失败,查看具体错误码和原因。
  • 验证密钥正确性:确保使用的是测试模式下的Stripe Secret Key和Webhook签名密钥,生产模式与测试模式密钥不通用。
  • 确认邮箱字段存在:部分测试支付场景中用户可能未提供邮箱,需在代码中保留空值判断逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 10:26:08