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

如何在Firebase云函数的两个导出函数之间传递用户邮箱数据

问题根源

Firebase Cloud Functions 是无状态的Serverless服务,每次函数调用可能被调度到不同的运行实例上,即便是同一个实例,闲置一段时间后冷启动也会重置全局变量,所以你没办法通过全局变量在两次不同的函数调用之间传递数据。


解决方案

利用Mpesa回调返回的唯一交易标识CheckoutRequestID做用户身份绑定,具体步骤如下:

  1. 前端发起Mpesa支付拿到CheckoutRequestID后,将该ID和当前登录用户的邮箱绑定存储到Firestore
  2. 收到Mpesa回调时,从回调参数中取出CheckoutRequestID,查询Firestore拿到对应用户邮箱
  3. 修复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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 09:27:02