82 lines
2.7 KiB
Python
82 lines
2.7 KiB
Python
"""订阅到期处理(Dramatiq actor + cron 每日调用)。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import date, timedelta
|
||
|
||
import dramatiq
|
||
from django.utils import timezone
|
||
|
||
from .models import Plan, Subscription
|
||
|
||
|
||
def expire_trials() -> dict:
|
||
"""扫描到期试用 → 降级 free 并把状态置为 expired,同时发通知。"""
|
||
from django.db.models import Q
|
||
|
||
from apps.notify.services import send_notification
|
||
|
||
now = timezone.now()
|
||
qs = Subscription.objects.filter(
|
||
status=Subscription.STATUS_TRIAL,
|
||
trial_ends_at__isnull=False,
|
||
trial_ends_at__lt=now,
|
||
).select_related("tenant", "plan")
|
||
|
||
free = Plan.objects.filter(code="free", is_active=True).first()
|
||
downgraded = 0
|
||
for sub in qs:
|
||
sub.status = Subscription.STATUS_EXPIRED
|
||
if free is not None:
|
||
sub.plan = free
|
||
sub.save(update_fields=["status", "plan", "updated_at"])
|
||
try:
|
||
send_notification(
|
||
tenant=sub.tenant,
|
||
title="【套餐】免费试用已到期",
|
||
content=(
|
||
"30 天全功能试用已结束,当前已回落至免费版配额"
|
||
"(1 用户 / 100 商品 / 50 单据每月)。"
|
||
"升级基础版 ¥998/月 可解除限制,历史数据全部保留。"
|
||
),
|
||
category="warning",
|
||
link="/#/pricing",
|
||
extra_data={"kind": "trial_expired"},
|
||
)
|
||
except Exception:
|
||
pass
|
||
downgraded += 1
|
||
|
||
# 付费到期(未续费)→ 同样降级
|
||
today = date.today()
|
||
paid_expired = Subscription.objects.filter(
|
||
status=Subscription.STATUS_ACTIVE,
|
||
period_end__isnull=False,
|
||
period_end__lt=today,
|
||
).select_related("tenant", "plan")
|
||
for sub in paid_expired:
|
||
sub.status = Subscription.STATUS_EXPIRED
|
||
if free is not None:
|
||
sub.plan = free
|
||
sub.save(update_fields=["status", "plan", "updated_at"])
|
||
try:
|
||
send_notification(
|
||
tenant=sub.tenant,
|
||
title="【套餐】订阅已到期",
|
||
content=f"套餐已于 {sub.period_end} 到期,当前回落至免费版配额,请及时续费。",
|
||
category="warning",
|
||
link="/#/pricing",
|
||
extra_data={"kind": "subscription_expired"},
|
||
)
|
||
except Exception:
|
||
pass
|
||
downgraded += 1
|
||
|
||
return {"expired_count": downgraded, "checked_at": now.isoformat()}
|
||
|
||
|
||
@dramatiq.actor(max_retries=2, time_limit=300_000)
|
||
def expire_trials_and_notify():
|
||
"""Dramatiq actor 包装(供 config/dramatiq.py 的 cron 调用/手动 send)。"""
|
||
return expire_trials()
|