基于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
相关产品推荐
相关产品推荐

