StripeのSubscription schedule APIを使ってサブスクの変更を予約する
なぜSubscription scheduleが必要になったのか
元々Flutter大学では、subscriptions.updateのAPIを使って、プランのダウングレードに伴う変更予約を行っていました。
しかし、invalid_request_error - billing cycle anchor
というエラーが特定の条件で起こるようになり、利用者の何人かにご迷惑をお掛けしてしましました。
これは例えば、「課題学習プラン6ヶ月15,400円」 → 「コミュニティプラン年額11,000円」のようなプラン変更予約があった時に発生しました。
Flutter大学では1年弱前から年額プランを導入していまして、長くいる方はお得に利用できるようになっています。そして半年ほど前から3ヶ月プランと6ヶ月プランも導入しています。
基本的に値段が上がるプラン変更に関しては、即時アップグレードで実装していた[1]ので、この問題は起こらなかったのですが、2024年1月にコミュニティプランの年額の大幅値下げを行いまして、コミュニティプラン年額にプラン変更予約した際の billing cycle anchor (請求サイクル) が変わるケースが出てきて、このエラーが発生したのです。
ちなみに請求サイクルが変わらない場合は、subscriptions.updateでpriceを変更して、次の決済の時には新しいpriceで決済されるという運用ができるので、問題ありませんでした。なので月額プランしかない場合はsubscriptions.updateでもプラン変更予約に対応できます。
しかし、今のFlutter大学には月額、3ヶ月、6ヶ月、年額が存在するのでそうは行きませんでした。
そこでSubscription ScheduleのAPIの登場
そこで、stripeサポートのdiscordに聞いてみたところ、このように請求サイクルが変わるプラン変更予約を実装する場合は、以下の記事のSubscription ScheduleのAPIを使うべきということが分かりました。
最終的には以下のようにfirebase functionsを作ってFlutter Webから呼び出して使っています。
// todo
export const createSubscriptionSchedules = functions.region('asia-northeast1').https.onCall(async (data, context) => {
const schedule = await stripe.subscriptionSchedules.create({
from_subscription: data.from_subscription,
});
const currentPhase = schedule.phases[0];
const currentPhaseItem = currentPhase.items[0];
const subscriptionSchedule = await stripe.subscriptionSchedules.update(schedule.id, {
phases: [
{
items: [
{
price: currentPhaseItem.price as string,
quantity: currentPhaseItem.quantity,
},
],
start_date: currentPhase.start_date,
end_date: currentPhase.end_date,
},
{
items: [
{
price: data.priceId,
quantity: 1,
},
],
coupon: data.couponId,
proration_behavior: data.proration_behavior,
metadata: data.metadata,
description: data.description,
start_date: data.start_date,
iterations: 1,
},
],
});
return subscriptionSchedule;
});
詳しくは、ドキュメントにもありますが、上記のコードでは、SubscriptionScheduleをまず作ってから、それをupdateするという処理をしています。
いきなりcreateのAPIだけで全ての必要パラメータを渡して行けるといいのですが、from_subscriptionというパラメータを使わなかった場合、新しいSubscriptionを作ってしまい、下図のようにサブスクが2つになってしまいます。(今回は基本的に変更予約に使うので既にSubscriptionがある)
サブスクが2つ作られて良くない状態
なので、from_subscriptionというparameterによって既存のSubscriptionからSubscirptionScheduleを作るという処理をしてからupdateしています。from_subscriptionを使う場合は他のパラメータを渡せないので、createとupdateに分ける必要があります。
以下が正しい状態です。
サブスクが1つで、そのサブスクに対して予約変更のスケジュールが存在した状態
予約変更のスケジュールが存在した状態の詳細画面
ここら辺が若干クセあるので難しいところでした。
懸念点
上記のようにcreateとupdateを分けなきゃ行けないので、createだけ成功してupdateが失敗したりしたら微妙だなというところです。しかし、これが公式ドキュメントと同じ解決方法なので、現状はベストなのかなと思います。
まとめ
Subscriptionの変更を予約する際は、このようにSubscriptionScheduleをうまく使ってみてください。
今回の実装を行なったサービスはFlutter大学です。Flutterの勉強に興味ある方や今バリバリFlutterやられている方で仲間が欲しい方はご活用ください!
参考文献
Discussion