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

基于Firebase与RevenueCat,年度订阅如何每月发放30000 Credits?

解决方案思路

要实现年度订阅每月自动发放Credits,需要结合订阅计划存储和定时触发函数补充RevenueCat事件的不足,核心逻辑如下:

  • 识别年度订阅的购买事件,在Firestore中创建用户的月度发放计划,记录剩余发放次数、下次发放时间等信息
  • 编写定时Cloud Function,每日检查待发放的计划,自动为符合条件的用户添加Credits并更新计划状态
  • 保留原有月度订阅的处理逻辑(依赖RevenueCat的RENEWAL事件)

具体实现步骤

1. 扩展原事件处理函数,记录年度订阅计划

修改监听events集合的函数,当检测到年度订阅的INITIAL_PURCHASE事件时,除首次发放Credits,还要在Firestore中新增subscription_schedules文档跟踪后续发放:

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const { addMonths, startOfMonth } = require("date-fns"); // 需先安装:npm install date-fns

admin.initializeApp();

// 替换为你实际的年度订阅SKU
const ANNUAL_SUBSCRIPTION_SKUS = ["annual_plan", "yearly_sub"];

exports.updateUserWordsOnNewEvent = functions.firestore
  .document("events/{eventId}")
  .onCreate(async (snapshot) => {
    const db = admin.firestore();
    const eventData = snapshot.data();
    const { type, app_user_id, product_id } = eventData;

    // 处理月度订阅的初始购买和续费
    if ((type === "RENEWAL" || type === "INITIAL_PURCHASE") && !ANNUAL_SUBSCRIPTION_SKUS.includes(product_id)) {
      await db.collection("users").doc(app_user_id).update({
        credits: admin.firestore.FieldValue.increment(30000) // 改用累加避免覆盖用户原有余额
      });
      return { success: true };
    }

    // 处理年度订阅的初始购买
    if (type === "INITIAL_PURCHASE" && ANNUAL_SUBSCRIPTION_SKUS.includes(product_id)) {
      // 首次发放Credits
      await db.collection("users").doc(app_user_id).update({
        credits: admin.firestore.FieldValue.increment(30000)
      });

      // 创建订阅发放计划:剩余11次发放,下次发放时间为下月初
      const nextDisbursementDate = startOfMonth(addMonths(new Date(), 1));
      await db.collection("subscription_schedules").add({
        userId: app_user_id,
        remainingDisbursements: 11,
        nextDisbursementDate: nextDisbursementDate,
        createdAt: new Date()
      });
      return { success: true };
    }
  });

2. 创建定时触发函数,自动执行月度发放

编写每日运行的定时函数,检查subscription_schedules中待发放的记录,执行Credits发放并更新计划:

exports.monthlyCreditDisbursement = functions.pubsub.schedule("0 0 * * *") // 每天凌晨0点执行
  .timeZone("Asia/Shanghai") // 根据用户时区调整
  .onRun(async (context) => {
    const db = admin.firestore();
    const today = startOfMonth(new Date()); // 取当月第一天,确保每月初发放

    // 查询今日需处理的订阅计划
    const schedulesQuery = db.collection("subscription_schedules")
      .where("nextDisbursementDate", "<=", today);

    const schedulesSnapshot = await schedulesQuery.get();
    if (schedulesSnapshot.empty) {
      console.log("No schedules to process today");
      return null;
    }

    // 批量处理确保原子性
    const batch = db.batch();
    for (const doc of schedulesSnapshot.docs) {
      const { userId, remainingDisbursements } = doc.data();

      // 给用户添加Credits
      const userRef = db.collection("users").doc(userId);
      batch.update(userRef, {
        credits: admin.firestore.FieldValue.increment(30000)
      });

      if (remainingDisbursements > 1) {
        // 更新计划:剩余次数减1,下次发放时间为下月初
        const nextDate = startOfMonth(addMonths(today, 1));
        batch.update(doc.ref, {
          remainingDisbursements: remainingDisbursements - 1,
          nextDisbursementDate: nextDate
        });
      } else {
        // 发放完成,删除计划文档
        batch.delete(doc.ref);
      }
    }

    await batch.commit();
    console.log(`Processed ${schedulesSnapshot.size} subscription schedules`);
    return null;
  });

3. 关键注意事项

  • 日期处理:用date-fns简化日期计算,避免时区误差,确保每月初准确发放
  • 并发安全:使用Firestore批量操作保证发放和计划更新的原子性,防止重复发放
  • SKU配置:ANNUAL_SUBSCRIPTION_SKUS必须替换为你实际的年度订阅产品ID,确保正确区分订阅类型
  • 余额累加:原代码直接覆盖credits的逻辑改为increment,避免清空用户原有余额

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 17:42:43