Stripe是一个流行的支付系统。而且是非常成熟的一个。这个服务有很多内置的功能,是所有当代企业在网上需要的。 这些功能的一个例子是订阅和计划。当一个订阅计划在结算周期中被改变时,我们可能希望只向客户收取当前结算周期剩余部分的新计划金额,并退回当前结算周期未使用的金额。 因此,订阅的价格是公平的,并每天计算。
例如,目前的计划费用为10美元。假设客户已经支付,新的计划费用为20美元。客户在计费周期的中间转换计划,新计划在当前计费周期剩余时间的每日金额是20美元的1/2,即10美元。 由于这是一个计划的升级,新计划的成本更高,我们可以直接向他们收取这10美元。 但等等,当前计划的每日金额不是10美元的1/2,是5美元。换句话说,客户已经为当前计费周期的剩余时间支付了一些钱。 因此,我们从收费中扣除这些钱。 结果,在这个例子中,我们只需要向客户收取5美元。 计算方法是10美元(当前计费周期的新计划剩余时间)-5美元(该期间已经支付的钱)。 这种计划变更的计算过程被称为比例计算。
从图形上看,它可以解释如下:

Stripe可以为我们计算比例,但这个功能只适用于每个座位的计划。在这篇文章中,您将看到如何从头开始建立一个用Ruby编写的计算比例的服务。它还展示了Ruby服务对象设计模式的实际应用。
按比例计算
使用Ruby编程语言和一些技巧,我们得到了下面这个类:
class PlanChangeProration
extend Dry::Initializer
extend Memoist
option :user
option :plan_id
def unused_time_cost
-(unused_time_line_item&.amount || 0)
end
def total
[remaining_time_cost - unused_time_cost, 0].max
end
private
def remaining_time_cost
new_plan.price * 100 * new_plan_remaining_time / new_plan_billing_cycle_time.to_f
end
def new_plan_billing_cycle_time
current_plan_used_time + new_plan_remaining_time
end
def current_plan_used_time
proration_date - (unused_time_line_item&.period&.start || proration_date)
end
def new_plan_remaining_time
(remaining_time_line_item&.period&.end || new_plan_period_end) - proration_date
end
def new_plan_period_end
proration_date + (new_plan.is_yearly? ? 365.days.to_i : 30.days.to_i)
end
memoize def new_plan
Plan.find(plan_id)
end
# For information about upcoming_invoice https://stripe.com/docs/billing/subscriptions/prorations
def upcoming_invoice
Stripe::Invoice.upcoming(
customer: customer.id,
subscription: subscription.id,
subscription_plan: new_plan.name,
subscription_proration_date: proration_date
)
end
memoize def customer
Stripe::Customer.retrieve(user.stripe_customer_id)
end
memoize def subscription
customer.subscriptions.first
end
memoize def proration_date
Time.now.to_i
end
# Stripe always generates 2 or 3 line items for the "upcoming invoice"
memoize def upcoming_line_items
return [] unless subscription
upcoming_invoice.lines.data
end
# Line item with description "Unused time on Medium Team after 16 Jun 2022" (Medium Team is current plan)
def unused_time_line_item
upcoming_line_items[0]
end
# Line item with description "Remaining time on xl_team_without after 16 Jun 2022" (xl_team_without is new plan)
def remaining_time_line_item
upcoming_line_items[1]
end
end
而这就是如何使用它:
service = PlanChangeProration.new(current_user, new_plan_id)
service.unused_time_cost # would return 5$ in our example
service.total # also 5$, but this is the charge amount
memoist和dry-initializer使这个服务变得干净和可读。
Stripe计划变更定额服务最初由Andrei Kaleshka于2022年6月18日在WideFix发布。