LoginSignup
4
3

More than 1 year has passed since last update.

Stripe Billingで、サブスクリプションのプラン・料金変更後の請求金額を事前にプレビューする

Posted at

Stripe Billingでサブスクリプションを提供している場合、契約期間中のプラン変更で「使った分だけ支払う」決済ができます。Stripeではこれを「比例配分」とよんでいます。

例えば「月1,000円のプラン」を途中で「月2,000円のプラン」に変更する場合、比例配分が有効になっていると、以下のような請求書が作られます。

  • 1: 月1,000円のプランの未使用分: ▲ 743円
  • 2: 月2,000円のプランの使用分:  1,485円
  • 3: 次回の契約期間の利用料金: 2,000円

この請求のうち、1と2が比例配分によるものです。
1は、「変更前プランの、プラン変更日から契約期間終了日までの日割料金」を請求から差し引いています。
そして2では、、「変更後プランの、プラン変更日から契約期間終了日までの日割料金」を新しく請求します。
3はこの次の契約期間の利用料金で、この3つを次の契約期間の請求書で決済することになります。(1と2だけ即時決済することも可能)

これにより、「プラン変更日前までは月1,000円のプラン、変更日からは月2,000円のプラン」の料金のみを顧客が支払ったことにできます。

スクリーンショット 0004-02-17 15.42.29.png

比例配分を有効化すると、次回の請求金額の見積もりが難しくなる

この比例配分があることで、顧客は「使った分だけ払えば良い」ことになり、プランのアップグレードへの懸念を減らすことができます。

ただし、「プランをいつ更新するか」によって次回の請求金額が変わるため、プラン変更画面で「次回の請求金額」を顧客に提示することが難しくなる問題が発生します。

また、Stripeでは日割り計算を秒単位で行うため、自前で金額計算の実装は難易度が高くなります。

InvoicesのRetrieve Upcoming APIを利用する

この比例配分された金額を計算するために利用できるのが、InvoicesのRetrieve Upcoming APIです。

このAPIを利用することで、「サブスクリプションの次回請求予定金額」を取得することができます。

const upcoming = await stripe.invoices.retrieveUpcoming({
   customer: 'cus_xxx',
   subscription: 'sub_xxxx',
})

このコードでは、指定した顧客・サブスクリプションの次回請求予定金額を取得します。
もし、プラン変更後の金額をプレビューしたい場合は、以下のように変更後のitemsを設定しましょう。

      const customerId = 'cus_xxx'
      const subscriptionId = 'sub_xxxx'
+      const subscription = await stripe.subscriptions.retrieve(subscriptionId)
+      const subscriptionItems = [...subscription.items.data.map(item => ({
+        id: item.id,
+        deleted: true,
+      })),{
+        price: 'price_xxxx',
+        quantity: 1
+      }]
      const upcoming = await stripe.invoices.retrieveUpcoming({
        customer: customerId,
        subscription: subscriptionId,
+        subscription_items: subscriptionItems,
      })

この際に注意が必要なのは、「変更前の料金データを、明示的に削除する」必要があることです。

上のコードでは、先に現在のサブスクリプションデータを取得し、契約中のプランを全て削除する操作が実装されています。

const subscription = await stripe.subscriptions.retrieve(subscriptionId)
const subscriptionItems = [...subscription.items.data.map(item => ({
  id: item.id,
  deleted: true,
})),{
  price: 'price_xxxx',
  quantity: 1
}]

もし削除せず、新しい料金データの設定だけを行うと、「追加でこの料金も契約する」と判断されますのでご注意ください。

プレビュー金額と一致させるため、サブスクリプションの更新タイミングを明示的に指定する

APIによって「いまプランを変更した場合、次回の請求がどうなるか」をプレビューできるようになりました。しかしまだ問題は残っています。

それは、「比例配分は秒単位で実施されるため、プレビューの金額と実際の請求額が完全に一致しない」問題です。

プレビューと更新を同じタイミングで実施することは不可能です。
そのため、「この時間に更新した場合、次回の請求はこの内容です」のように、「更新タイミングを指定」する必要があります。

Stripeで更新タイミングを指定するには、proration_dateを利用します。

以下のサンプルコードでは、Day.jsを利用し、「その日の終わりにプラン変更を実行する」設定を行なっています。

      const customerId = 'cus_xxx'
      const subscriptionId = 'sub_xxxx'
+      const prorationDate = dayjs().endOf('date')
      const subscription = await stripe.subscriptions.retrieve(subscriptionId)
      const subscriptionItems = [...subscription.items.data.map(item => ({
        id: item.id,
        deleted: true,
      })),{
        price: 'price_xxxx',
        quantity: 1
      }]

      // 請求書のプレビューを取得する場合
      const upcoming = await stripe.invoices.retrieveUpcoming({
        customer: customerId,
        subscription: subscriptionId,
        subscription_items: subscriptionItems,
-        subscription_proration_date: prorationDate
+        subscription_proration_date: prorationDate.unix()
      })

      // サブスクリプションを実際に更新する場合
      await stripe.subscriptions.update(subscriptionId, {
+        proration_date: prorationDate.unix(),
        items: subscriptionItems,
      })

プレビューの明細を確認する

APIで取得したデータには、合計金額だけでなく明細も含まれています。
明細の表示には、upcoming.lines.dataのデータを利用します。

      const upcoming = await stripe.invoices.retrieveUpcoming({
        customer: customerId,
        subscription: subscriptionId,
        subscription_items: subscriptionItems,
        subscription_proration_date: prorationDate.unix()
      })

      let amount = 0
      upcoming.lines.data.forEach((line, i ) => {
        console.log(`[${i}]${line.description} - ${line.amount}`)
        amount = amount + line.amount
      })

      console.log({
        amount,
        upcomingAmount: upcoming.amount_due,
      })

このコードを実行すると、明細と次回請求金額のプレビューが表示されます。

  console.log
    [0]Unused time on Starter plan after 15 Feb 2022 - -814

  console.log
    [1]Remaining time on Business plan after 15 Feb 2022 - 1221

  console.log
    [2]1 × Business plan (at ¥1,500 / month) - 1500


  console.log
    { amount: 1907, upcomingAmount: 1907 }

明細には以下の3種類が含まれています。

  • Unused: 変更前のプランの未使用分(変更予定日から本来の更新予定日までの日割り分)
  • Remaining: 変更後のプランの使用分(変更予定日から本来の更新予定日までの日割り分)
  • 変更後のプラン: 次回のサイクルの請求金額

after 15 Feb 2022部分が、更新予定日の日付です。
現時点では、Stripe側では明細内容は完全に翻訳されません。
そのため、顧客からの問い合わせが想定される場合には、あらかじめヘルプドキュメントなどを作成しましょう。

またここで表示されている金額を全て足し合わせると、upcoming.amount_dueの金額と一致します。
もし0円を下回る場合は、0円として処理され、マイナスの金額はその次以降の請求内容と相殺されます。

契約期間(インターバル)が異なる料金への変更

月額から年額への変更などでも、「subscription.update APIが実行可能であれば」プレビューを取得できます。
1つのサブスクリプションには、1つの契約期間しか設定できません。
そのため、契約期間の異なる料金へ変更する場合は、必ずそれまで利用していた料金全てを削除する必要があります。

      const subscription = await stripe.subscriptions.retrieve(subscriptionId)
      const subscriptionItems = [...subscription.items.data.map(item => ({
        id: item.id,
        deleted: true,
      })),{
        price: 'price_xxxx',
        quantity: 1
      }]
      const upcoming = await stripe.invoices.retrieveUpcoming({
        customer: customerId,
        subscription: subscriptionId,
        subscription_items: subscriptionItems,
      })

「複数の契約期間が混在する状態」ですと、以下のようにエラーが発生しますので、ご注意ください。

StripeInvalidRequestError: Currency and interval fields must match across all plans on this subscription. Found mismatch in interval field.

従量課金プランへの変更について

従量課金プランへの変更についても、「更新可能な設定」であればプレビューができます。

ただし、従量課金プランの場合、使用量計測前の金額でプレビューされるため、あまり意味を成さないことにご注意ください。

段階的・数量ベースで、定額課金も含むプランの場合

基本的には、どのプランでもプレビューは可能です。

ただし、設定によっては、請求書明細の行数が長くなる場合がありますので、デザインやCSSの設定にはご注意ください。

スクリーンショット 0004-02-17 16.08.51.png

関連ドキュメント

[PR] Stripe開発者向け情報をQiitaにて配信中!

  • [Stripe Updates]:開発者向けStripeアップデート紹介・解説
  • ユースケース別のStripe製品や実装サンプルの紹介
  • Stripeと外部サービス・OSSとの連携方法やTipsの紹介
  • 初心者向けのチュートリアル(予定)

など、Stripeを利用してオンラインビジネスを始める方法について週に2〜3本ペースで更新中です。

-> Stripe Organizationsをフォローして最新情報をQiitaで受け取る

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3