核心概念
订阅生命周期
订阅不是"付钱就完了"——试用、激活、续费、升降级、取消、过期,每一步都有对应的状态和处理逻辑。Pay4SaaS 把这整套生命周期都管理好了。
状态流转
┌─────────┐
│ pending │ ← 发起结账
└────┬────┘
│
┌──────────┴──────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ trialing │──付款──▶│ active │◀── 续费成功 / 恢复订阅
└──────────┘ └────┬─────┘
│
┌───────────┬───────┼───────┬───────────┐
▼ ▼ ▼ ▼ ▼
┌───────────┐ ┌────────┐ ┌──────┐ ┌────────┐
│ canceling │ │ paused │ │expired│ │past_due│→ 按 expired 处理
└─────┬─────┘ └────────┘ └──────┘ └────────┘
│
│ 到期
▼
┌──────────┐
│ canceled │
└──────────┘各状态说明
| 状态 | 说明 | 用户能否使用服务 |
|---|---|---|
pending | 已发起结账,等待支付 | 否 |
trialing | 试用期中 | 是(无限订阅模式下可用) |
active | 已付费,正常使用 | 是 |
canceling | 用户已取消,但当前周期未到期 | 是(到期前仍可使用) |
canceled | 已取消且已到期 | 否 |
expired | 已过期(付款失败或自然到期) | 否 |
paused | 支付平台暂停了订阅,防止平台侧已停止扣款但用户仍能使用 | 否 |
past_due | 付款逾期,系统直接按过期处理 | 否 |
试用期
配置试用期后(见定价模式),用户首次订阅时可以免费试用。
关键规则:
- 每个用户只能试用一次,系统通过
trial_start字段追踪 - 已试用过的用户再次订阅时,系统自动使用无试用版本的商品 ID
- 试用期间,无限订阅模式下用户可以正常使用服务
- 配额订阅模式下,没有试用这一回事,但你可以给用户送积分体验。
- 试用期结束后自动扣款,扣款成功则转为
active
升级与降级
系统通过比较实际价格来判断升级还是降级:新价格 > 当前价格 = 升级,反之 = 降级。
升级(立即生效)
- 新计划立即生效
- 支付方式自动按比例计算差价(prorated)
- 配额订阅模式下,配额按新计划重置
降级(下个周期生效)
- 当前周期不变,保留原计划权益
- 系统写入
pending_plan_type和pending_plan_billing_cycle - 下次续费 Webhook 到达时,自动应用降级
- 配额订阅模式下,当前周期保留原配额
前端可以通过 status.details 查看待生效的降级信息:
const { status } = useAccess()
if (status.details.pendingPlanType) {
console.log(`下个周期将切换为: ${status.details.pendingPlanType}`)
console.log(`生效时间: ${status.details.pendingPlanEffectiveAt}`)
}取消与恢复
取消
取消分两种:
- 到期取消(默认): 状态变为
canceling,当前周期结束前仍可使用,到期后变为canceled - 立即取消: 状态直接变为
canceled,立即失效
恢复
用户在 canceling 状态下(还没到期)可以撤销取消:
- 状态恢复为
active - 清除取消相关字段
- 继续正常扣费
续费
续费是自动的。支付方式在每个计费周期结束前自动扣款,通过 Webhook 通知系统:
- 收到续费成功的 Webhook(如
invoice.paid) - 系统更新
current_period_start和current_period_end - 如果有待生效的降级计划,此时自动应用
- 配额订阅模式下,重置月度配额
如果续费失败:
- 收到付款失败的 Webhook(如
invoice.payment_failed) - 系统将订阅状态标记为
expired,立即停止服务 - 支付方式后台会继续重试扣款
- 如果后续扣款成功,收到成功 Webhook 后自动恢复
重复订阅处理
如果用户在已有订阅的情况下又发起了一次订阅:
- 相同计划: 延长到期时间(把新周期叠加到已有订阅上)
- 不同计划: 取消旧订阅,激活新订阅
乱序保护
支付方式的 Webhook 可能乱序到达(比如"取消"先于"激活"到达)。系统通过以下机制防护:
- 每个状态变更操作都检查当前状态是否允许转换
updateSubscriptionPeriod只允许周期向前推进expireSubscription检查current_period_end是否已过markSubscriptionCanceled只允许从active/trialing/canceling转换