#Stripe API / サブスクリプションのスケジュール登録で終了日を設定してキャンセルした時の、日割計算的な調整額 = インヴォイスを確認した / with Ruby


ポイント

  • スケジュール登録が既にアクティブで、サブスクリプションが開始している時の動作確認、テスト
  • サブスクリプションが開始している場合は、既に請求は発行されており、サブスクリプションの期間を変えると、次回請求の内容がやりくりされるみたいだ。
  • 1日期間のプランのサブスクリプションで、きっちり1日でサブスクリプション期間が終わるように end_date を指定した場合は、次回の請求も発生せず、ただ素直にサブスクリプションがキャンセルされるようだ。
  • 1日期間のプランのサブスクリプションで、既に請求が発行されており、なおかつそれを半日に縮めた場合、次回請求の日割計算の調整として、プランの半額が返金されるような挙動を見せた。さらにそれを元の1日に戻した場合は、さらに次回請求がやりくりされて、次回請求が0円になるみたいだ。値段のつじつまは合っているので正しい。
  • SubscriptionSchedule の end_behaviour は デフォルトの release ではなく cancel である必要がある。
  • この記事を書いた後で分かったが、どうやら SubscriptionSchedule の作成時にも日割計算の無効オプションを指定できるらしい ( prorate = false ) 。少し試したところ、この時の挙動もなんだか同じように見えたが、どういう時に挙動が変わってくるのだろう。追って調査したい。

Docs

https://stripe.com/docs/billing/subscriptions/subscription-schedules
https://stripe.com/docs/billing/subscriptions/subscription-schedules/use-cases
https://stripe.com/docs/api/subscription_schedules/release
https://support.stripe.com/questions/create-update-and-schedule-subscriptions

Stripeの基本

日本正式リリースしたStripeを使ってサブスクリプション型決済システムを実装する - Qiita
https://qiita.com/tady/items/7617e62b2a5402ebd0fb

Stripe Billing 101 - Qiita
https://qiita.com/y_toku/items/235b5e7ee00792edcbbf

Stripe初心者のための基本的な使い方(Rails編) - Qiita
https://qiita.com/ryouzi/items/6ee8f277471aa3b02f7b

Code


require 'stripe'

Stripe::api_key = ENV['STRIPE_SECRET_KEY']

product1 = Stripe::Product.create(name: "Gold plan #{rand(9999999999)}")
plan1 = Stripe::Plan.create(interval: 'day', currency: 'jpy', amount: 5000, product: product1.id, usage_type: 'licensed')

tax_rate = Stripe::TaxRate.create(display_name: 'Tax Rate', percentage: 10.0, inclusive: false)
customer = Stripe::Customer.create
payment_method = Stripe::PaymentMethod.create(type: 'card', card: { number: '4242424242424242', exp_year: 2030, exp_month: 01})
customer_payment_method = Stripe::PaymentMethod.attach(payment_method.id, customer: customer.id)

def put_subscription_schedule(subscription_schedule, message)
  puts '-' * 100
  puts "Subscription Schedule"
  puts message
  puts '-' * 100
  puts subscription_schedule
  puts '-' * 100
  puts "https://dashboard.stripe.com/test/subscription_schedules/#{subscription_schedule.id}"
end

# https://stripe.com/docs/api/subscription_schedules/create
subscription_schedule = Stripe::SubscriptionSchedule.create(
  {
    customer: customer.id,
    start_date: Time.now.to_i + 5,
    default_settings: {
      default_payment_method: customer_payment_method.id,
    },
    phases: [
      {
        plans:
          [
            { plan: plan1.id, quantity: 1 },
          ],
        default_tax_rates: [tax_rate],
      },
    ],
  }
)
put_subscription_schedule(subscription_schedule, 'CREATED')

puts '-' * 100
puts "Wait until subscription schedule starts"
puts '-' * 100
until subscription_schedule.status == 'active' do
  subscription_schedule = Stripe::SubscriptionSchedule.retrieve(subscription_schedule.id)
  puts subscription_schedule.status
  sleep 2
end


def update_subscription_schedule(base_subscription_schedule, end_from_start_date: )
  Stripe::SubscriptionSchedule.update(
    base_subscription_schedule.id,
    {
      end_behavior: 'cancel',
      phases: [
        {
          plans:
            [
              {
                plan:     base_subscription_schedule.phases[0].plans[0].plan,
                quantity: base_subscription_schedule.phases[0].plans[0].quantity
              },
            ],
          default_tax_rates: [base_subscription_schedule.phases[0].default_tax_rates[0].id],
          start_date: base_subscription_schedule.phases[0].start_date,
          end_date: base_subscription_schedule.phases[0].start_date + end_from_start_date
        },
      ]
    }
  )
end


# STEP A
# Set subscrition canceled in one day, fit to plan natural cycle "day"
# It does not create proration adjustment or Upcoming invoice Because subscription schedule cycle is smart finishing.
#
# Happens events kinds are ...
#  subscription_schedule.updated
updated_subscription_schedule = update_subscription_schedule(subscription_schedule, end_from_start_date: 24*60*60)
put_subscription_schedule(updated_subscription_schedule, 'UPDATED')

# STEP B
# Set subscrition canceled in half a day.
# Not wait natural plan interval end.
# It makes proration adjustment half price of plan in Upcoming invoice.
#
#   Unused time on Gold plan xxxxxxxx after 02 Jan 2020 / 1 / -¥2,500
#
#   Subtotal -¥2,500
#   Tax Rate (10%) -¥250
#   Total -¥2,750
#   Applied balance ¥2,750
#   Amount due ¥0
#
# Happens events kinds are ...
#   subscription_schedule.updated
#   customer.subscription.updated
#   invoiceitem.created
updated_subscription_schedule = update_subscription_schedule(subscription_schedule, end_from_start_date: 12*60*60)
put_subscription_schedule(updated_subscription_schedule, 'UPDATED')

# STEP C
# Set subscription back natural full ony day.
# It makes Upcoming Invoice plice zero
#
#   Time on Gold plan xxxxxxxx after 02 Jan 2020 / 1 / ¥2,500
#   Unused time on Gold plan xxxxxxxx after 02 Jan 2020 / 1 / -¥2,500
#
#   Subtotal ¥0
#   Tax Rate (10%) ¥0
#   Total ¥0
#   Amount due ¥0
updated_subscription_schedule = update_subscription_schedule(subscription_schedule, end_from_start_date: 24*60*60)
put_subscription_schedule(updated_subscription_schedule, 'UPDATED')

# STEP D
#   Gold plan 5392253972 / 1 / ¥5,000 / ¥5,000
#   Unused time on Gold plan 5392253972 after 02 Jan 2020 / 1 / -¥2,500
#
#   Subtotal ¥2,500
#   Tax Rate (10%) ¥250
#   Total ¥2,750
#   Amount due ¥2,750
updated_subscription_schedule = update_subscription_schedule(subscription_schedule, end_from_start_date: 36*60*60)
put_subscription_schedule(updated_subscription_schedule, 'UPDATED')

BEFORE UPDATE

無期限のサブスクリプションであるため、終了日はセットされていない
次回請求もスケジューリングされている


STEP A

Ends was set
サブスクリプションがちょうど現在期間で終わるように設定した場合
終了日がセットされている
次回請求は消えた

STEP B

Upcoming invoice occurs
現在期間のサブスクリプション期間を半日に縮めた場合
次回請求での調整額が発生している

STEP C

next Upcoming invoice occurs
現在期間のサブスクリプション期間を半日に縮めた後、1日に戻した場合
次回請求での調整額に対してさらに調整額が発生して、請求は0円になっている

STEP D

Original by Github issue