如何在Firebase云函数的两个导出函数之间传递用户邮箱数据
问题根源
Firebase Cloud Functions 是无状态的Serverless服务,每次函数调用可能被调度到不同的运行实例上,即便是同一个实例,闲置一段时间后冷启动也会重置全局变量,所以你没办法通过全局变量在两次不同的函数调用之间传递数据。
解决方案
利用Mpesa回调返回的唯一交易标识CheckoutRequestID做用户身份绑定,具体步骤如下:
- 前端发起Mpesa支付拿到
CheckoutRequestID后,将该ID和当前登录用户的邮箱绑定存储到Firestore - 收到Mpesa回调时,从回调参数中取出
CheckoutRequestID,查询Firestore拿到对应用户邮箱 - 修复mpesa函数无返回导致的超时问题
修改后的代码示例
Flutter端
发起支付拿到交易ID后,调用云函数存储关联关系:
// 发起Mpesa支付,获取返回的CheckoutRequestID var checkoutRequestID = await mpesaFlutterPlugin.startTransaction(/* 支付参数 */); // 绑定交易ID和当前登录用户邮箱 await FirebaseFunctions.instance.httpsCallable('savePaymentMapping').call({ 'checkoutRequestID': checkoutRequestID, 'userEmail': loggedInUser.email });
Cloud Functions index.js
const functions = require("firebase-functions"); const admin = require('firebase-admin'); admin.initializeApp(); const db = admin.firestore(); function convertTZ(date, tzString) { return new Date((typeof date === "string" ? new Date(date) : date).toLocaleString("en-US", {timeZone: tzString})); } // 新增:存储交易ID与用户邮箱的映射关系 exports.savePaymentMapping = functions.https.onCall(async (data) => { const { checkoutRequestID, userEmail } = data; await db.collection('payment_mappings').doc(checkoutRequestID).set({ userEmail: userEmail, createdAt: admin.firestore.FieldValue.serverTimestamp() }); return { success: true }; }); // 优化后的mpesa回调处理函数 exports.mpesa = functions.https.onRequest(async (request, response) => { try{ const data = request.body; const stkCallback = data.Body.stkCallback; const checkoutRequestID = stkCallback.CheckoutRequestID; // 查询当前交易对应的用户邮箱 const mappingDoc = await db.collection('payment_mappings').doc(checkoutRequestID).get(); if (!mappingDoc.exists) { functions.logger.info(`未找到交易${checkoutRequestID}对应的用户`); return response.status(200).send('OK'); } const loggedInUser = mappingDoc.data().userEmail; const date_ob = Date(); const date = convertTZ(date_ob,"Africa/Nairobi"); functions.logger.info("Mpesa info", loggedInUser, data); functions.logger.info(stkCallback.ResultCode); // 批量写入保证数据一致性 const batch = db.batch(); batch.set(db.collection('transactions').doc(`${date}`), data); // 建议优化:不要直接用邮箱做文档ID,否则新交易覆盖旧交易,可改为子文档存储 batch.set(db.collection('user_transactions').doc(loggedInUser).collection('records').doc(`${date}`), data); await batch.commit(); // 必须返回响应,否则会触发函数超时 response.status(200).send('OK'); } catch(e){ functions.logger.error(e); response.status(500).send('Error'); } });
注意事项
- 可定期清理
payment_mappings集合中超过7天的旧数据,避免存储冗余 - 不要直接用用户邮箱作为交易文档的ID,否则新交易会覆盖该用户的历史交易记录,建议使用子集合存储用户的所有交易数据
内容的提问来源于stack exchange,提问作者Edward
相关产品推荐
相关产品推荐

