はじめに
サブスクリプションプランを複数用意したいことってよくあると思うのですが、例えば月間契約と年間契約で料金と期間のみ異なるプランの時、ただ二つ用意するだけでは重複購入ができてしまいます。
そんなときGoogleBillingLibraryではプランの切り替え(アップグレードやダウングレード)ができます。
公式ドキュメントに詳しく書いてあるので迷うことは少ないかと思いますが、備忘録も兼ねて書いておこうと思います。
課金実装については触れず既に複数プランが用意してある前提で進みます。
環境
Kotlin 1.3.61
GoogleBillingLibrary 3.0.3'
追加する部分
追加するコードはとても少なく以下の通りです。
private fun showBillingDialog(skus: List<String>, scope: CoroutineScope, activity: Activity){
//省略
val params = BillingFlowParams.newBuilder()
.setOldSku(purchase,purchaseToken)//ここと
.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION)//ここを追加
.setSkuDetails(skuDetails[0])
.build()
//省略
}
.setOldSku()に現在購入中のアイテム名とpurchaseTokenを渡します(後述します)。
.setReplaceSkusProrationModeは公式ドキュメントを参考に切り替えモードを決めます。
切り替えモード「DEFERRED」について
公式は同じ階層での契約期間の変更(月単位から年単位など)時に「DEFERRED」を推奨している感じですが、「DEFERRED」の場合、購入の流れが正常に行われても実際に購入されるのは現在購入中プランの更新時であり、それまでの間は再度他のプランへの変更はできず、他のプランの購入フローに入ろうとするとレスポンスコード5でエラーとなります。 また、ユーザーの購入操作と実際の購入のタイミングに差異があるため、購入ができたのかどうかが分かりづらいような気がしますし、承認をどうするべきかもよく分かりません。 なので個人的には「IMMEDIATE_WITHOUT_PRORATION」が無難かなあと思います。基本的にはこれだけで自動的にプランのアップグレードやダウングレードができるのですが、このままだと新規で購入する場合におかしなことになってしまいます。
なのでif文などで適当に分けます。
private fun showBillingDialog(skus: List<String>, scope: CoroutineScope, activity: Activity){
//省略
val params:BillingFlowParams
if (purchase == "none" || purchase == skus[0]){//現在購入中のアイテムがないor現在購入中のアイテムと同じ
params = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetails[0])
.build()
}else{//現在購入中のアイテムがある時はアップグレードorダウングレード
params = BillingFlowParams.newBuilder()
.setOldSku(purchase,purchaseToken)
.setReplaceSkusProrationMode(BillingFlowParams.ProrationMode.IMMEDIATE_WITHOUT_PRORATION)
.setSkuDetails(skuDetails[0])
.build()
}
//省略
}
purchaseというStringにはpurchaseQuery()時に現在購入中のアイテム名を入れるようにしていて、購入中のアイテムが無ければ"none"としています(この辺は仕様によって色々やり方が変わると思いますが一応以下の感じです)。
購入中のアイテムがある場合は、ついでにpurchaseTokenも保持しておきます。
private fun purchaseQuery(){
val purchaseResult = billingClient.queryPurchases(BillingClient.SkuType.SUBS)
if (purchaseResult.responseCode == BillingClient.BillingResponseCode.OK){
val purchases = purchaseResult.purchasesList
if (!purchases.isNullOrEmpty()){//購入中のアイテムがあるとき
purchases.forEach { p ->
purchase = p.sku
purchaseToken = p.purchaseToken
}
}else{
purchase = "none"
}
}else{
//error
}
}
おしまい
かなり雑な説明になってしまいましたが参考になれば幸いです。