Stripe支付成功/取消时Webhook未创建Firestore文档问题
Stripe Webhook未触发Firestore文档创建的问题排查与修复
核心问题分析
- Webhook端点与函数名不匹配:你在Stripe控制台配置的Webhook指向
handleStripePayments,但代码中导出的是handleStripePaymentSuccess和handleStripePaymentFailure两个独立函数,导致事件无法正确路由。 - 缺少Webhook签名验证:未验证Stripe请求的签名,可能导致请求被拒绝或处理无效数据。
- Firestore初始化冗余:同时使用
firebase-admin和@google-cloud/firestore初始化数据库,可能引发冲突。 - 事件处理逻辑漏洞:
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
相关产品推荐
相关产品推荐

