如何在Stripe中实现自定义月末到期的月度订阅计费周期?
自定义月度订阅计费周期实现方案
核心思路
通过Stripe的billing_cycle_anchor(计费周期锚点)或subscription_schedule(订阅计划),结合Node.js日期计算逻辑,让首周期截止到当月最后一天,后续自动按自然月续费,全程不依赖试用期。
具体实现步骤
1. 编写当月最后一天时间戳计算函数
先实现工具函数,精准计算订阅当日对应的当月最后一天UTC秒级时间戳(Stripe API要求的时间格式):
function getMonthEndTimestamp(subscribeDate = new Date()) { const year = subscribeDate.getUTCFullYear(); const month = subscribeDate.getUTCMonth(); // 取下个月第一天0点,减1毫秒得到当月最后一天23:59:59.999 UTC const monthEnd = new Date(Date.UTC(year, month + 1, 1, 0, 0, 0, 0)); monthEnd.setUTCMilliseconds(monthEnd.getUTCMilliseconds() - 1); return Math.floor(monthEnd.getTime() / 1000); }
2. 基于支付链接的两种实现方案
方案A:Webhook拦截修改订阅
利用Stripe的checkout.session.completed事件,在用户完成支付后立即调整订阅的计费锚点:
const stripe = require('stripe')('你的Stripe密钥'); // Webhook处理路由 app.post('/stripe-webhook', express.raw({type: 'application/json'}), async (req, res) => { const sig = req.headers['stripe-signature']; const webhookSecret = '你的Webhook密钥'; let event; try { event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret); } catch (err) { return res.status(400).send(`Webhook Error: ${err.message}`); } if (event.type === 'checkout.session.completed') { const session = event.data.object; // 仅处理订阅类型的结账会话 if (session.mode === 'subscription' && session.subscription) { const subscriptionId = session.subscription; const monthEndTimestamp = getMonthEndTimestamp(); // 更新订阅锚点,关闭自动计价避免首月金额拆分 await stripe.subscriptions.update(subscriptionId, { billing_cycle_anchor: monthEndTimestamp, proration_behavior: 'none', payment_behavior: 'default_incomplete' }); } } res.json({received: true}); });
注意:需在Stripe后台启用
checkout.session.completed事件,确保Webhook地址能被Stripe正常访问。
方案B:提前创建订阅计划关联支付链接
在用户进入支付流程前,先定义好订阅周期规则,避免Webhook延迟:
- 创建自定义订阅计划:
async function createCustomSchedule(customerId, priceId) { const monthEndTimestamp = getMonthEndTimestamp(); const nextMonthStart = monthEndTimestamp + 86400; // 下月第一天UTC时间戳 return stripe.subscriptionSchedules.create({ customer: customerId, start_date: 'now', end_behavior: 'release', // 计划结束后转为普通订阅 phases: [ { items: [{price: priceId}], end_date: monthEndTimestamp, // 首周期截止当月最后一天 }, { items: [{price: priceId}], iterations: null, // 无限循环自然月续费 interval: 'month', interval_count: 1, start_date: nextMonthStart, } ] }); }
- 创建支付链接时关联该计划:
const paymentLink = await stripe.paymentLinks.create({ line_items: [{ price: priceId, quantity: 1, }], subscription_data: { schedule: '上述创建的订阅计划ID', }, });
3. 关键注意事项
- 计价控制:若需首月收取全额月费而非按天拆分,需设置
proration_behavior: 'none',或在创建价格时指定recurring.usage_type: 'licensed'。 - 时区处理:所有日期计算使用UTC时间,避免时区差异导致周期错误。
- 权限配置:确保Stripe API密钥拥有
subscriptions:update和subscription_schedules:create权限。
替代方案
若不想依赖Webhook或订阅计划,可在用户发起订阅前手动创建临时价格:设置首月计费周期为订阅日到当月最后一天,后续自动切换至正常月度价格。但此方式需维护临时价格资源,灵活性不如前两种方案。
内容的提问来源于stack exchange,提问作者javed iqbal
相关产品推荐
相关产品推荐

