Android

Android 利用In-App Billing 內購 APP內付款

Android 利用In-App Billing 內購 APP內付款

  1. 先將權限與Library導入Project
  2. 包一版APK給內部測試後,新增商品(等待審核通過才有資料)
  3. 創建購買監聽與客戶監聽
  4. 讀取商品資料
  5. 付款UI
  6. 紀錄所有商品
  7. 調用付款
  8. 效果展示

1.先將權限與Library導入Project

AndroidManifest.xml

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

build.gradle(Module)

def billing_version = "3.0.3"
implementation "com.android.billingclient:billing-ktx:$billing_version"

2.包一版APK給內部測試後,新增商品(等待審核通過才有資料)

營利>產品>應用程式內商品

設定自己的產品(ID是為了 比對產品)

3.創建購買監聽與客戶監聽

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

    private lateinit var billingClient: BillingClient
    var skuDetails: MutableLiveData<Map<String, SkuDetails>> = MutableLiveData()

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    fun create() {
        billingClient = BillingClient
            .newBuilder(app.applicationContext)
            .setListener(this)
            .enablePendingPurchases()
            .build()

        if (!billingClient.isReady) {
            billingClient.startConnection(this)
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun destroy() {
        if (billingClient.isReady) {
            billingClient.endConnection()
        }
    }

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

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

    //設置完成
    override fun onBillingSetupFinished(billingResult: BillingResult) {
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
            GlobalScope.launch(Dispatchers.IO) {
                try {
                    querySkuDetails()
                } catch (e: Exception) {
                    Toast.makeText(app.applicationContext, "讀取資料失敗", Toast.LENGTH_SHORT).show()
                } finally {
                    cancel()
                }
            }
        }
    }

    //關閉連線
    override fun onBillingServiceDisconnected() {
        billingClient.startConnection(this)
    }

    //購買後
    override fun onPurchasesUpdated(
        billingResult: BillingResult,
        purchase: MutableList<Purchase>?
    ) {
        when (billingResult.responseCode) {
            BillingClient.BillingResponseCode.OK -> {
                purchase?.let {
                    Toast.makeText(app.applicationContext, "購買成功", Toast.LENGTH_SHORT).show()
                }
            }

            BillingClient.BillingResponseCode.USER_CANCELED -> {
                Toast.makeText(app.applicationContext, "使用者取消", Toast.LENGTH_SHORT).show()
            }

            BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED -> {
                Toast.makeText(app.applicationContext, "ITEM_ALREADY_OWNED", Toast.LENGTH_SHORT)
                    .show()
            }

            BillingClient.BillingResponseCode.DEVELOPER_ERROR -> {
                Toast.makeText(app.applicationContext, "DEVELOPER_ERROR", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

4.讀取商品資料

private suspend fun querySkuDetails() {
    val params = SkuDetailsParams.newBuilder()
        .setType(BillingClient.SkuType.INAPP)
        .setSkusList(listOf(SKU.ProductOne.id, SKU.ProductThree.id))
        .build()

    val skuDetailsResult = withContext(Dispatchers.IO) {
        billingClient.querySkuDetails(params)
    }

    when (skuDetailsResult.billingResult.responseCode) {
        BillingClient.BillingResponseCode.OK -> {
            skuDetailsResult.skuDetailsList?.also {
                skuDetails.postValue(
                    HashMap<String, SkuDetails>().apply {
                        it.forEach {
                            put(it.sku, it)
                        }
                    }
                )
            } ?: also {
                skuDetails.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 -> {
            Log.e("error", "錯誤請稍後在試")
        }
        BillingClient.BillingResponseCode.USER_CANCELED,
        BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED,
        BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED,
        BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> {
            Log.e("error", "錯誤請稍後在試")
        }
    }
}

5.付款UI

fun launchBillingFlow(activity: Activity, params: BillingFlowParams): Int {
    if (!billingClient.isReady) {
        Toast.makeText(app.applicationContext, "請稍後在試", Toast.LENGTH_SHORT).show()
    }
    val billingResult = billingClient.launchBillingFlow(activity, params)
    return billingResult.responseCode
}

6.紀錄所有商品

enum class SKU(val id: String) {
    ProductOne("1"),
    ProductThree("3"),
    //用於測試用
    AndroidTestPurchased("android.test.purchased"),
    AndroidTestCanceled("android.test.canceled"),
    AndroidTestRefunded("android.test.refunded"),
    AndroidTestItemUnavailable("android.test.item_unavailable")
}

7.調用付款

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_login)

    //綁定LifeCycle
    lifecycle.addObserver(BillingClientLifecycle.getInstance(application))
}

fun grass(view: View) {
    BillingClientLifecycle.getInstance(application).skuDetails.observe(this, Observer {
        it.forEach { skuDetail ->
            if (skuDetail.key == SKU.ProductOne.id) {
                BillingClientLifecycle.getInstance(application).launchBillingFlow(
                    this,
                    BillingFlowParams.newBuilder().setSkuDetails(skuDetail.value).build()
                )
            }
        }
    })
}

fun box(view: View) {
    BillingClientLifecycle.getInstance(application).skuDetails.observe(this, Observer {
        it.forEach { skuDetail ->
            if (skuDetail.key == SKU.ProductThree.id) {
                BillingClientLifecycle.getInstance(application).launchBillingFlow(
                    this,
                    BillingFlowParams.newBuilder().setSkuDetails(skuDetail.value).build()
                )
            }
        }
    })
}

8.效果展示

發表迴響