Laravel Cashier:订阅续订时重置使用额度的实现方案
解决方案:订阅周期续订时重置额度
1. 调整数据库结构(核心前提)
当前users表仅存tokens_left无法满足周期重置需求,必须补充订阅周期相关信息,以下两种方案任选:
方案A:扩展users表
新增字段关联订阅周期与额度规则:
| id | tokens_left | subscription_plan | current_cycle_start | current_cycle_end | quota_per_cycle |
|---|---|---|---|---|---|
| 1 | 5 | premium | 2024-05-01 00:00:00 | 2024-06-01 00:00:00 | 10 |
| 2 | 9 | premium | 2024-05-05 00:00:00 | 2024-06-05 00:00:00 | 10 |
字段说明:
subscription_plan:标记用户当前订阅类型(如premium)current_cycle_start/end:记录当前订阅周期的起止时间quota_per_cycle:该订阅计划的周期额度上限
方案B:单独创建subscription_quotas表
若需保留历史额度使用记录,建议拆分出独立表:
| id | user_id | plan_type | cycle_start | cycle_end | total_quota | used_quota |
|---|---|---|---|---|---|---|
| 1 | 1 | premium | 2024-05-01 00:00:00 | 2024-06-01 00:00:00 | 10 | 5 |
| 2 | 2 | premium | 2024-05-05 00:00:00 | 2024-06-05 00:00:00 | 10 | 1 |
剩余额度可通过total_quota - used_quota直接计算,同时便于回溯用户过往周期的使用情况。
2. 两种重置触发方式
方式一:实时触发(推荐,基于Stripe Webhook)
Stripe订阅续订会触发invoice.paid事件(确保订阅成功扣费),通过监听该事件可实时重置额度:
- 在Stripe控制台配置Webhook端点,接收
invoice.paid事件 - 解析事件中的
subscription对象,获取customer(关联你的用户ID)、current_period_start/current_period_end(新周期时间) - 对应更新数据库:
- 方案A:将
tokens_left设为quota_per_cycle,同时更新周期起止时间 - 方案B:为用户创建新的额度记录,
used_quota设为0,填充新周期时间
- 方案A:将
示例伪代码(Python):
def handle_stripe_webhook(request): event = stripe.Webhook.construct_event( request.data, request.headers['Stripe-Signature'], WEBHOOK_SECRET ) if event.type == 'invoice.paid': subscription = event.data.object.subscription # 通过Stripe客户ID关联自有用户 user = User.objects.get(stripe_customer_id=subscription.customer) # 重置额度并更新周期 user.tokens_left = user.quota_per_cycle user.current_cycle_start = datetime.fromtimestamp(subscription.current_period_start) user.current_cycle_end = datetime.fromtimestamp(subscription.current_period_end) user.save() return HttpResponse(status=200)
方式二:定时任务(兜底方案)
为避免Webhook延迟或失败,可配合每日定时任务检查到期周期:
- 每日执行脚本,查询所有
current_cycle_end早于当前时间的用户 - 重置其额度并更新新周期时间(按原周期长度顺延,比如加30天)
示例SQL查询(筛选需重置用户):
SELECT id, quota_per_cycle FROM users WHERE subscription_plan = 'premium' AND current_cycle_end < NOW();
重置操作SQL:
UPDATE users SET tokens_left = quota_per_cycle, current_cycle_start = NOW(), current_cycle_end = DATE_ADD(NOW(), INTERVAL 30 DAY) WHERE id = {user_id};
3. 实时校验时的兼容处理
在用户触发任务使用时,先检查是否已进入新周期,若已到期则自动重置额度,避免Webhook延迟导致的业务异常:
def use_task(user_id): user = User.objects.get(id=user_id) # 检查周期是否过期 if datetime.now() > user.current_cycle_end: user.tokens_left = user.quota_per_cycle user.current_cycle_start = datetime.now() user.current_cycle_end = datetime.now() + timedelta(days=30) user.save() # 扣减额度 if user.tokens_left > 0: user.tokens_left -= 1 user.save() return True return False
内容的提问来源于stack exchange,提问作者furkankufrevi
相关产品推荐
相关产品推荐

