在使用 Stripe API 更新订阅(Subscription)时,虽然 API 本身并不复杂,但某些情况下,由于账单日期或产品订阅类型的原因,我们无法立即更新订阅。这时,Stripe 的 Subscription Schedule 功能就显得尤为重要。它允许我们在未来某个时间点创建或更新订阅,从而满足复杂的业务需求。
Subscription Schedule 的强大功能
Subscription Schedule 是一个功能强大的工具,但初次使用时可能会让人感到困惑。尽管 Stripe 提供了详尽的 API 文档 和 Use Cases 来解释其用法,但由于 API 的不断更新,某些字段可能会被弃用,或者新的对象被引入,导致文档与实际操作存在差异。例如,Price
已经取代了 Plan
,而在 Phase Items 中,coupon
字段也被弃用。
在本文中,我将分享一个实际案例:如何在未来某个时间点安排更新单个 Subscription Item 的数量(quantity),同时保留现有的优惠券(coupon)。我还将讨论在这一过程中遇到的挑战及其解决方案。
场景设定
假设我们有一个 Subscription,其中包含一个 Subscription Item——SaaS Member Fee,单价为 $10,数量为 5,并且附带一个三个月的免费优惠券。我们的目标是在 9 月 1 日将数量更新为 10,从而实现以下效果:
如果你只想快速查看解决方案,可以直接跳到 解决方案 部分。
Stripe 内部的实现方式
尽管有详细的 API 文档和用例,初次对接 Subscription Schedule API 时,仍然会感到困惑。例如,我们知道需要创建一个 Subscription Schedule,但具体如何传递参数仍不清楚。
对于创建 Schedule 的 API POST /v1/subscription_schedules
,文档中提供的示例如下:
ruby
Stripe::SubscriptionSchedule.create({
customer: ‘cus_NcI8FsMbh0OeFs’,
start_date: 1680716828,
end_behavior: ‘release’,
phases: [
{
items: [
{
price: ‘price_1Mr3YcLkdIwHu7ixYCFhXHNb’,
quantity: 1,
},
],
iterations: 12,
},
],
})
然而,这个示例并不适用于我的场景,因为我们需要修改一个已经存在的 Subscription。文档中提到,可以使用 from_subscription
参数,它接受一个现有 Subscription 的 ID。文档还指出,如果使用了这个参数,其他参数(如 phase 值)就不能再设置,建议通过两次 API 调用来实现。
通过观察 Stripe 自身的操作,我们可以发现,Stripe 也是通过两次 API 调用来实现这一功能的:
第一次 API 请求
ruby
POST /v1/subscription_schedules
Request Body:
{
from_subscription: “sub_1PeCNNLiQSlGYPd5Wpmvb1n6”
}
Response:
{
id: “sub_sched_1PeH6LLiQSlGYPd599aOVdoW”,
# … 其他字段省略
}
第二次 API 请求
ruby
POST /v1/subscription_schedules/sub_sched_1PeH6LLiQSlGYPd599aOVdoW
Request Body:
{
proration_behavior: “none”,
phases: [
{
start_date: 1721378477,
end_date: “now”,
iterations: “”,
discounts: [
{
coupon: “EDHtWtdk”
}
],
default_tax_rates: “”,
automatic_tax: {
enabled: false
},
items: [
{
price: “price_1PeCLRLiQSlGYPd50cFz00Na”,
quantity: 5
}
],
collection_method: “charge_automatically”
},
{
start_date: “now”,
end_date: 1725217200,
discounts: [
{
coupon: “EDHtWtdk”
}
],
default_tax_rates: “”,
items: [
{
quantity: 5,
tax_rates: “”,
price: “price_1PeCLRLiQSlGYPd50cFz00Na”
}
]
},
{
start_date: 1725217200,
discounts: [
{
coupon: “EDHtWtdk”
}
],
default_tax_rates: “”,
items: [
{
quantity: 10,
tax_rates: “”,
price: “price_1PeCLRLiQSlGYPd50cFz00Na”
}
],
proration_behavior: “none”,
collection_method: “charge_automatically”,
invoice_settings: {
description: “Thank you for your business!”
}
}
],
end_behavior: “release”
}
关键概念解析
什么是 Phase?
Phase 是 Subscription Schedule 中的一段特定时间,在这段时间内会应用所指定的 Price、Charge、Tax 等参数。一个 Phase 的关键参数包括:
- start_date 和 end_date:标识每个 Phase 的开始和结束时间。
- price 和 quantity:为订阅项目指定不同的价格和数量。
- discounts:在该 Phase 中使用的特定折扣和优惠券。
- tax_rates 和 automatic_tax:税率相关参数。
从上述请求中可以看到,Stripe 设定了 3 个 Phase:
- Phase#0:立即终止当前订阅。
- Phase#1:从当前时间到预期时间,保持原有订阅参数。
- Phase#2:在预期时间更新数量为 10。
实际上,使用两个 Phase 也是可行的。
什么是 Proration 和 Proration Behavior?
Proration 是指在订阅计划发生变化时,按照比例调整账单金额。Stripe 提供了三种 proration_behavior 选项:
- create_prorations:默认值,按比例调整账单。
- none:不进行按比例调整。
- always_invoice:立即创建按比例调整的账单。
例如,如果客户在账单周期中间升级了订阅,选择 create_prorations
会按比例计算剩余天数的费用,而选择 none
则不会进行任何调整。
什么是 End Behavior?
end_behavior
定义了 Schedule 结束时 Subscription 的行为,其值可以是 release
或 cancel
:
- release:默认值,Subscription 保持当前状态继续。
- cancel:Subscription 直接结束。
需要注意的是,Stripe 只允许一个 Subscription 最多有一个激活状态的 Schedule。如果遇到错误 “You cannot migrate a subscription that is already attached to a schedule”,说明当前 Subscription 已经有一个 Schedule,此时需要先 release 或 cancel 它。
解决方案
假设我们有一个 subscription
,希望在 time
时修改其 item 的 quantity
。我们只需要使用两个 Phase:
ruby
schedule = Stripe::SubscriptionSchedule.create from_subscription: subscription.id
phase0 = schedule.phases[0]
phase0_item = phase0.items[0]
original_quantity = phase0_item.quantity
price_id = phase0_item.price
discounts = phase0.discounts.collect(&:to_hash)
phases = [
# Phase 0 – 修改现有订阅阶段,结束日期为 time
{
start_date: phase0.start_date,
end_date: time.to_i,
proration_behavior: ‘none’,
discounts: discounts,
items: [
{
price: price_id,
quantity: original_quantity,
}
]
},
# Phase 1 – 设置一个新的订阅阶段,开始日期为 time
,数量为 quantity
{
start_date: time.to_i,
end_date: time.to_i + 60,
proration_behavior: ‘none’,
discounts: discounts,
items: [
{
price: price_id,
quantity: quantity,
}
]
}
]
Stripe::SubscriptionSchedule.update schedule.id, phases: phases, proration_behavior: ‘none’
结语
Stripe 的 Subscription Schedule 功能为订阅管理提供了极大的灵活性。尽管初次使用时可能会遇到一些挑战,但通过深入理解其机制和多次实践,我们可以充分发挥其潜力。希望本文能帮助你更好地理解和使用 Subscription Schedule。