close

我們發佈 APP 有很大的一份部就是為了賺錢吧

那麼就不可不知,如何實作 APP 內購功能

無論是一次性的遊戲藥水,還是週期性的訂閱服務

用 2019 Google IO 推出的 Google Play Billing Library 一次做給你看

 

在開始之前我們有一些程式外的資訊需要設定

1、取得開發者帳號

2、在 Google Play Console 設定付款資訊 (金流用的)

3、把 1 & 2 關聯起來

https://developer.android.com/google/play/billing/getting-ready#account

4、在 Google Play Console 建立商品 (需先上傳有權限 apk)

https://support.google.com/googleplay/android-developer/answer/1153481

 

以上跟著 Google 的引導做,應該就可以了

完成設定且建立好商品的人應該可以在 Google Play Console 看到以下畫面

截圖 2020-07-20 下午5.50.15

截圖 2020-07-20 下午5.50.25

 

 

再來就是 APP 這裡要開始實作 Library 了

首先要有內購的權限,在 manifast 加入下列

<!--    內購-->
<uses-permission android:name="com.android.vending.BILLING" />

 

在 app/gradle dependencies 加入依賴 

// APP 內購,Google Play In-App billing.
implementation 'com.android.billingclient:billing-ktx:3.0.0'

 

rebuild 之後,我們來寫一個 lifecycle observer

用來管理 BillingClient 的生命週期

class BillingClientLifecycle private constructor(
    private val app: Application
) : LifecycleObserver, 
PurchasesUpdatedListener, 
BillingClientStateListener {

private lateinit var billingClient: BillingClient

companion object {
    @Volatile
    private var INSTANCE: BillingClientLifecycle? = null

    fun getInstance(app: Application): BillingClientLifecycle =
        INSTANCE ?: synchronized(this) {
            INSTANCE ?: BillingClientLifecycle(app)
                            .also { INSTANCE = it }
        }
}

 

可以看到我們用 singleton 來生成這個 class

並 impletment 

LifecycleObserver, PurchasesUpdatedListener, BillingClientStateListener

 

然後 LifecycleObserver 這邊的 onCreate 實作如下

@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun create() {
    PLog.d("$TAG: ON_CREATE")

    billingClient = BillingClient
        .newBuilder(app.applicationContext)
        .setListener(this)
        .enablePendingPurchases() // Not used for subscriptions.
        .build()

    if (!billingClient.isReady) {
        PLog.d("$TAG: BillingClient: Start connection...")
        billingClient.startConnection(this)
    }
}

 

在 onCreate 我們做了連線至 Billing Library 的動作

所以相對的,我們也會在 onDestory 進行 disconnection

@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun destroy() {
    PLog.d("$TAG: ON_DESTROY")

    if (billingClient.isReady) {
        PLog.d("$TAG: closing connection")
        // After calling endConnection()
        // we must create a new BillingClient.
        billingClient.endConnection()
    }
}

 

這樣就控制住了連線與斷線

再來我們就可以利用 BillingClientStateListener 做事情

比如說:連線後查詢 or 斷線後重連

/** === BillingClientStateListener Start === */
override fun onBillingSetupFinished(
                  billingResult: BillingResult) {
    val responseCode = billingResult.responseCode
    val debugMessage = billingResult.debugMessage

    PLog.d("$TAG: 
    onBillingSetupFinished: $responseCode $debugMessage")

    if (responseCode == BillingClient.BillingResponseCode.OK) {
        // The billing client is ready. 
        GlobalScope.launch(Dispatchers.IO) {
            try {
                querySkuDetails()
                queryPurchases()
            } catch (e: Throwable) {
                PLog.e(e, "$TAG query:")
            } finally {
                this.cancel()
            }
        }
    }
}

override fun onBillingServiceDisconnected() {
    PLog.d("$TAG: onBillingServiceDisconnected")
    // Try connecting again with exponential backoff.
    billingClient.startConnection(this)
}
/** === BillingClientStateListener End === */

 

一切都設設定好之後

就來看看怎麼 query 吧

suspend fun querySkuDetails() {
        PLog.d("$TAG: querySkuDetails")

        val params = SkuDetailsParams.newBuilder()
            .setType(BillingClient.SkuType.SUBS)
            .setSkusList(
                listOf(SKU.Test001.id, SKU.Test002.id)
            ).build()

        val skuDetailsResult = withContext(Dispatchers.IO) {
            billingClient.querySkuDetails(params)
        }
        val billingResult = skuDetailsResult.billingResult
        val responseCode = billingResult.responseCode
        val debugMessage = billingResult.debugMessage
        PLog.d("$TAG: 
        onSkuDetailsResponse: $responseCode $debugMessage")

        PLog.obj(skuDetailsResult)
       
        when (responseCode) {
            BillingClient.BillingResponseCode.OK -> {
                skuDetailsResult.skuDetailsList?.also {
                    skusWithSkuDetails.postValue(
                        HashMap<String, SkuDetails>().apply {
                            it.forEach {
                                put(it.sku, it)
                            }
                        }.also {
                            PLog.d("$TAG: onSkuDetailsResponse:
                                          count ${it.size}")
                        }
                    )
                } ?: also {
                    PLog.d("$TAG: onSkuDetailsResponse: 
                                  null SkuDetails list")
                    skusWithSkuDetails.postValue(emptyMap())
                }
            }
            BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
            BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
            BillingClient.BillingResponseCode.BILLING_UNAVAILABLE,
            BillingClient.BillingResponseCode.ITEM_UNAVAILABLE,
            BillingClient.BillingResponseCode.DEVELOPER_ERROR,
            BillingClient.BillingResponseCode.ERROR -> {
                PLog.e("$TAG: onSkuDetailsResponse:
                        $responseCode $debugMessage")
            }
            BillingClient.BillingResponseCode.USER_CANCELED,
            BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED,
            BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED,
            BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> {
                // These response codes are not expected.
                PLog.d("$TAG: onSkuDetailsResponse: 
                        $responseCode $debugMessage")
            }
        }
    }

 

附註:

SKU 是我用來記錄商品 ID 的一個 enum class

SkuTpye 有兩種 IN_APP & SUBS

skusWithSkuDetails 是用來通知更新 UI 的 LiveData

 

至此,我們完成了第一階段取得商品的部份

再來就可以進入下一階段,啟動購買流程囉!

Android Kotlin APP 應用程式內購買 In-App Billing with Google Play Billing Library 實作教學、範例 example ( similar iOS In App Purchase ) ( 二 ) 』Willy's Fish教學筆記
 

若有遇到商品列表為空的朋友們

截圖 2020-07-21 下午4.16.45

可以看這一篇

Android Kotlin  實作教學/範例 APP 應用程式內購買 In-App Billing with Google Play Billing Library (( Q:Why dose my List of SkuDetail return empty from google?』Willy's Fish教學筆記

 

 

 

資料來源:

https://developer.android.com/google/play/billing

https://github.com/android/play-billing-samples

 

 

arrow
arrow

    顏澤偉 發表在 痞客邦 留言(0) 人氣()