Android 利用In-App Billing 內購 APP內付款
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()
)
}
}
})
}