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

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

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

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

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

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

![スクリーンショット 0004-02-17 15.42.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/af4ed028-5d5d-bdc5-815b-ce7ae4fec00b.png)

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

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

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

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

# InvoicesのRetrieve Upcoming APIを利用する

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

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

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

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

```diff_javascript
      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,
      })
```

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

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

```js
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](https://day.js.org/)を利用し、「その日の終わりにプラン変更を実行する」設定を行なっています。

```diff_javascript
      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`のデータを利用します。

```js
      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,
      })
```

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

```bash
  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 }
```

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

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

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

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

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

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


```js
      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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/7892c929-3f2d-6ce3-97b7-c067659d6316.png)


# 関連ドキュメント

https://stripe.com/docs/api/invoices/upcoming

https://stripe.com/docs/billing/subscriptions/prorations#preview-proration


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

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

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

-> [Stripe Organizationsをフォローして最新情報をQiitaで受け取る](https://qiita.com/organizations/stripe)