19
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AtraeAdvent Calendar 2017

Day 22

AndroidでGoogle Play月額課金を実装する

Last updated at Posted at 2017-12-22

最近やる機会のあった、Androidのアプリ内課金(月額課金)の実装のメモ。

ライブラリの設定

まずは実装に必要なGoogle Play Billing Libraryを追加。

app/build.gradle
compile 'com.android.billingclient:billing:1.0'

Googleで調べたりすると、IInAppBillingServiceをプロジェクトに追加してゴニョゴニョ…ってやり方がよく出てくるが、最近は上記のライブラリを使えば良さそう。

パーミッションをAndroidManifest.xmlに追加する必要もない。

アプリ内購入機能への接続開始・終了

基本的にBillingClientクラスを利用して処理を書いていく。

startConnectionでクライアントのセットアップ・サービスへの接続を開始し、その引数のBillingClientStateListeneronBillingServiceDisconnectedonBillingSetupFinishedでその結果を受け取ることが可能。後者の引数には、整数のレスポンスコード(BillingClient.BillingResponse)が入っており、その値に応じて処理を書いていくことになる。

BillingActivity.kt
class BillingActivity : AppCompatActivity(), PurchasesUpdatedListener {
    private lateinit var billingClient: BillingClient

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        billingClient = BillingClient.newBuilder(this).setListener(this).build()
        startServiceConnection(null)
    }

    override fun onDestroy() {
        super.onDestroy()
        billingClient.endConnection()
    }

    private fun startServiceConnection(executeOnSuccess: Runnable?) {
        if (billingClient.isReady) {
            executeOnSuccess?.run()
            return
        }
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingServiceDisconnected() {}

            override fun onBillingSetupFinished(@BillingClient.BillingResponse responseCode: Int) {
                when (responseCode) {
                    BillingClient.BillingResponse.OK -> executeOnSuccess?.run()
                    else -> {}
                }
            }
        })
    }

    // PurchasesUpdatedListener
    override fun onPurchasesUpdated(@BillingClient.BillingResponse responseCode: Int, purchases: MutableList<Purchase>?) {
    }
}

billingClientのsetListenerの引数はPurchasesUpdatedListenerで、ユーザがアイテムを購入したときなどに呼ばれるonPurchasesUpdatedを提供する。こちらの引数もBillingClient.BillingResponse

サービスへの接続を終了する際は、endConnectionを利用。アクティビティのライフサイクルに合わせて、onDestroyなどで実行する。

購入可能なアイテムの取得

BillingClientを通して、Google Playコンソールで作成したアプリ内で購入できるアイテムを非同期に取得することができる。情報取得に必要なパラメータSkuDetailsParamsに購入アイテムのIDや課金タイプ(今回は月額課金)を指定して、querySkuDetailsAsyncで取得開始。このメソッドの2番目の引数はSkuDetailsResponseListenerとなっており、アイテムの情報取得結果が通知されるリスナになっている。

BillingActivity.kt
class BillingActivity : AppCompatActivity(), PurchasesUpdatedListener, SkuDetailsResponseListener {

    private fun retrieveSubscriptionItems() {
        val executeOnConnectedService = Runnable {
            val params: SkuDetailsParams.Builder = SkuDetailsParams.newBuilder()
                    .setSkusList(listOf("itemA", "itemB"))
                    .setType(BillingClient.SkuType.SUBS)
            billingClient.querySkuDetailsAsync(params.build(), this)
        }
        startServiceConnection(executeOnConnectedService)
    }

    // SkuDetailsResponseListener
    override fun onSkuDetailsResponse(@BillingClient.BillingResponse responseCode: Int, skuDetailsList: MutableList<SkuDetails>?) {
        when (responseCode) {
            BillingClient.BillingResponse.OK -> {
                if (skuDetailsList != null) {
                    skuDetailsList.forEach { ... }
                    return
                }
            }
            else -> {}
        }
    }
}

正常に取得できた際に受け取れるSkuDetailsクラスを通して、各アイテムの価格や説明などを得ることができる。

購入

購入にはbillingClientのlaunchBillingFlowを利用する。
購入に必要なパラメータはBillingFlowParamsが提供している。アイテム名や購入タイプを指定するほか、ユーザを一意に識別する文字列やアップグレード/ダウングレード時の請求のオプションなどを指定する。
また、既に別のアイテムを購入済で、新しい購入の扱いがダウングレード/アップグレードとなる場合、addOldSkuで購入済のアイテムのIDを指定しておかないといけない。

BillingActivity.kt
class BillingActivity : AppCompatActivity(), PurchasesUpdatedListener, SkuDetailsResponseListener {

   override fun startBillingFlow() {
        val executeOnConnectedService = Runnable {
            val billingFlowParamsBuilder = BillingFlowParams.newBuilder()
                    .setSku("itemA")
                    .setAccountId("XXXXXXXXXXXXXXX")
                    .setReplaceSkusProration(true)
                    .setType(BillingClient.SkuType.SUBS)
                    .setVrPurchaseFlow(false)
            billingClient.launchBillingFlow(this, billingFlowParamsBuilder.build())
        }
        startServiceConnection(executeOnConnectedService)
    }

    // PurchasesUpdatedListener
    override fun onPurchasesUpdated(@BillingClient.BillingResponse responseCode: Int, purchases: MutableList<Purchase>?) {
        when (responseCode) {
            BillingClient.BillingResponse.OK -> {
                if (purchases != null && purchases.isNotEmpty()) {
                    ....
                }
            }
            BillingClient.BillingResponse.FEATURE_NOT_SUPPORTED -> {}
            BillingClient.BillingResponse.ITEM_ALREADY_OWNED -> {}
            BillingClient.BillingResponse.USER_CANCELED -> {}
            else -> {}
        }
    }
}

最初に書いたとおり、購入の結果はonPurchasesUpdatedに通知され、レスポンスに応じて処理を分けていく。

購入済のアイテムの取得

購入済のアイテムを取得する。Google Playストアアプリのキャッシュを利用するため、ネットワークを介さずに同期的に取得可能。

BillingActivity.kt
val purchasesList: List<Purchase> = billingClient.queryPurchases(BillingClient.SkuType.SUBS).purchasesList

参考

[PR] 仲間探してます

アトラエで一緒にネイティブアプリやWeアプリを書いてくれる仲間を募集中です!
https://www.green-japan.com/company/172

19
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?