こんにちは、モバイルクライアントグループの中島です。
Studyplusでは3月17日に、有料会員サービスである Studyplus Pro をiOS/Android両OSでリリースしました。
Studyplus Pro の開発では、アプリ内課金について様々な要件を調査しました。 今回は、調査したものの中でも知見が少ないと感じたGoogle Play Billing Library による グレード切り替え についてお話ししたいと思います。
なお、執筆時に利用している Billing Library のバージョンは2.2.0
です。
Studyplus Pro の定期購入プランについて
Studyplus Proでは以下の2つのプランを用意しています。
- 600円/月
- 4,800円/年
ただ、大変申し訳ありませんが諸事情により、現在Androidでは年額プランには未対応です。
導入までもう少々お待ちいただければと思います。
Google Playで定期購入のプランを切り替える
以下は Billing Library において、切り替えを考慮しない定期購入でGoogleの購入画面を開く実装です。
val flowParams = BillingFlowParams.newBuilder() .setSkuDetails(skuDetails) .build() val responseCode = billingClient.launchBillingFlow(activity, flowParams)
余談ですが、SkuDetails(Google Playにおける課金アイテムの情報を持ったクラス)は自身が通常の消費商品/定期購入商品のどちらなのかについて情報を保持しています。 そのため、消費商品の場合と定期購入の場合でここの実装に違いはありません。
本題に入ります、 Billing Library では、定期購入のアップグレード/ダウングレードがサポートされています。
アプリにおいては変更点も少なく、簡単に実装できます。
val flowParams = BillingFlowParams.newBuilder() .setSkuDetails(newSkuDetails) .setOldSku(oldPurchase.sku, oldPurchase.purchaseToken) // ← Purchaseクラスは購入済みアイテムの情報をまとめたクラス .build() billingClient.launchBillingFlow(activity, flowParams)
切り替える先のSkuDetailsのほか、setOldSku()
で現在購入済みの購入情報をセットします。
セットするのはSku(SkuDetailsではなくIDの文字列のみ)と購入のユニーク判定に用いられるPurchaseToken
の2つです。
これらがセットされていると、Billing Libraryの方で判断して切り替えであることがわかるUIを出してくれます。
なお、公式ドキュメントでセットしているのはskuのみですが、リリースノートを見るとBilling Library のバージョン2.2.1
からはPurchaseToken
を入れることが推奨されています。
以下、該当部分の実装を抜粋します。
/** @deprecated */ @Deprecated @NonNull public BillingFlowParams.Builder setOldSku(String var1) { this.zzc = var1; return this; } @NonNull public BillingFlowParams.Builder setOldSku(String var1, String var2) { this.zzc = var1; this.zzd = var2; return this; }
サーバー側については構成に依りますが、Google Playではグレード切り替え前後でPurchaseToken
という購入のユニーク判定に用いる値が変わります。
そのため、切り替え前のPurchaseToken
に紐づいた登録情報を無効にする必要があります。
更新方法としては、
- Google Playのリアルタイムデベロッパー通知を用いて更新する
- 切り替え後の
PurchaseToken
をアプリから受け取った際に、その購入を検証するAPIのレスポンスからlinkedPurchaseToken
を参照して更新する
の二通りという認識です。本記事の趣旨からは外れるため、今回の紹介はここまでにさせてください。
Studyplus Pro では、このアップグレード/ダウングレード機能を利用して、月額プランと年額プランの切り替えを行う予定です。
プランの切り替えモードについて
今回の本題です。
前節では setOldSku()
のみ追加すればOKと言いましたが、実はそれ以外にも考慮すべきところがあります。
Billing Library ではアップグレード/ダウングレードの際に、比例配分モード
というものを設定できます。
val flowParams = BillingFlowParams.newBuilder() .setSkuDetails(skuDetails) .setOldSku(oldPurchase.sku, oldPurchase.purchaseToken) .setReplaceSkusProrationMode(replaceSkusProrationMode) // ← 比例配分モード設定 .build() billingClient.launchBillingFlow(activity, flowParams)
以下公式ドキュメントから抜粋します。
フラグ名 | 挙動 |
---|---|
IMMEDIATE_WITH_TIME_PRORATION |
切り替えは直ちに有効になり、新しい有効期限が比例配分され、ユーザーへの入金または請求が行われます。これが現在のデフォルトの動作です。 |
IMMEDIATE_AND_CHARGE_PRORATED_PRICE |
切り替えは直ちに有効になりますが、請求期間は変わりません。残りの期間の価格に対する請求が行われます。 注: このオプションは定期購入のアップグレードでのみ利用可能です。 |
IMMEDIATE_WITHOUT_PRORATION |
切り替えは直ちに有効になり、新しい価格が次回契約期間に請求されます。請求期間は変わりません。 |
DEFERRED |
切り替えは次回契約期間に有効になります。 |
公式ドキュメントの詳細説明では、月額200円と月額300円という「同じ期間でサービス内容が異なる」ケースで比較しています。 なので、Studyplus Pro のように「長く登録してもらう代わりに割引する」といったプラン形式の場合にはどうするのかを改めて検証する必要がありました。
検証結果
実際に動かして検証した結果をまとめます。
各モードの挙動
IMMEDIATE_WITH_TIME_PRORATION
- デフォルトの変更方法
- ストア上における所有プランは即変更
- 日割計算して残り日数を変更する
- 変更時の支払いはなし
月額->年額
の場合、月額分の600円がすでに払われているので、4,800円/年で換算して1ヶ月半分が先払いされた換算になる。そのため次回更新日が1ヶ月半後となる年額->月額
の場合、年額分の4,800円がすでに払われているので、600円/月で換算して8ヶ月分が先払いされた換算になる。そのため次回更新日が8ヶ月後となる- 次回更新日まで何回でも他方へ変更(買い直し)が可能、毎購入時に残り期間をベースに金額を計算し、所有しているプランの割合に換算して継続する
IMMEDIATE_AND_CHARGE_PRORATED_PRICE
アップグレードにしか使用できない
- ここでいうアップグレードとは、時間単位で金額が高い方に変更することを言う
- Studyplus Pro でいうと
年額->月額
ストア上における所有プランは即変更
- 日割計算して追加金額を支払う
- 次回更新日は最初に買ったプランのもので固定されている
月額->年額
の場合、エラーが返ってきて購入できない年額->月額
の場合、年額分の4,800円がすでに払われている。残り1年分を月額プランの金額で払い直すことになる、600円/月で換算して1年分は7,200円。なので、差額の2,400円を払って残りの期間のプランをグレードアップする換算になる- 例えば半年後に切り替えた場合は2400円分が余っている状態となり、600円/月で換算して残りの半年分3,600円が必要となるので、差額の1,200円を払って残りの半年をグレードアップした状態にする
プランごとでサービス内容に差があるときに利用すべき
変更方法と思われる- 例として公式ドキュメントの例のように200円/月、300円/月というような形
IMMEDIATE_WITHOUT_PRORATION
- ストア上における所有プランは即変更
- 更新日は最初に買ったプランのもので固定されている
- 変更時の支払いはなし
- アップグレード/ダウングレードに差異はない
- 次回更新日まで何回でも他方へ変更(買い直し)が可能、更新日時点で所有しているプランを用いて継続する
DEFERRED
- ストア上における所有プランは次回更新日に変更。それまでは旧プランのみ所持の扱いで、ストア上の所有プランに新プランの追加はされない
- 変更時の支払いはなし
- アプリから Billing Library を通して所有プランをフェッチしても旧プランしか確認できない
- 更新日まで、旧プランは所有済みなので購入できない、新プランは別のエラーが表示されて購入できない -> 次回更新日まで変更ができなくなる
- アップグレード/ダウングレードに差異はない
- iOSの期間変更挙動に最も近いと思われたが、切り替え購入した際にレシートが返ってこないためアプリからサーバに反映するタイミングがない
- 更新日に切り替わるがそこから3日以内に購入の承認をしないと払い戻されてしまう、リアルタイムデベロッパー通知で変更取得が必要と思われる
参考:iOSの挙動
iOSのApp Storeによる定期購入では、Google Playとかなり事情が異なります。 いわゆるアップグレード/ダウングレードに当たる変更と、同一サービス内容での期間変更でそれぞれ切り替え時の挙動が一意に定義されています。 Studyplus Pro では現在後者のみをサポートしています。
https://help.apple.com/app-store-connect/#/dev75708c031help.apple.com
- ストア上における現在有効なプランは次回更新日に変更。それまでは旧プラン新プラン両方所持の扱い
- 更新日は最初に買ったプランのもので固定されている
- 変更時の支払いはなし
- そもそもアップグレード/ダウングレードという概念が分かれているので
月額->年額
/年額->月額
に違いはない
iOSで切り替え時の挙動を任意に変えられないことから、Studyplus Pro ではAndroidでもなるべくそちらに近いモードを選択しようとなっていました。
調査結果から
iOSに一番近いと思われるDEFERRED
ですが、再度の切り替えができないなどの取り回しの悪さが懸念点として上がります。
そのため、Studyplus Pro では次に近いと思われるIMMEDIATE_WITHOUT_PRORATION
を利用して実装する予定です。
備考
IMMEDIATE~~の3つは変更操作直後にプランが切り替わる際に前のプラン購入は即時無効になります。 この影響で、プラン変更時の購入承認操作に失敗した場合は(3日後に)払い戻しが起こり、Google Play上では完全に未登録の扱いになります。 変更操作のみがキャンセルになるのではないので注意が必要です。
まとめ
今回は、Google Playを通したアプリ内課金による定期購入における、購入プランの切り替えモードについてお話しいたしました。
なかなか実装の機会がないものかと思いますが、Google側で様々な用意がされているんだなと実感しました。 Billing Libraryも公言されている1年に1回の更新だけでなく、ktxの追加なども細かくされていますし、今後も色々注目していきたいところです。