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円のプラン」の料金のみを顧客が支払ったことにできます。
比例配分を有効化すると、次回の請求金額の見積もりが難しくなる
この比例配分があることで、顧客は「使った分だけ払えば良い」ことになり、プランのアップグレードへの懸念を減らすことができます。
ただし、「プランをいつ更新するか」によって次回の請求金額が変わるため、プラン変更画面で「次回の請求金額」を顧客に提示することが難しくなる問題が発生します。
また、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の設定にはご注意ください。
関連ドキュメント
[PR] Stripe開発者向け情報をQiitaにて配信中!
- [Stripe Updates]:開発者向けStripeアップデート紹介・解説
- ユースケース別のStripe製品や実装サンプルの紹介
- Stripeと外部サービス・OSSとの連携方法やTipsの紹介
- 初心者向けのチュートリアル(予定)
など、Stripeを利用してオンラインビジネスを始める方法について週に2〜3本ペースで更新中です。