본문 바로가기

Android 공부/Android Library Study

인앱결제 - 구매후 생각해야 할 것

1. 구매후 로직이 필요했다.

 이전에 올린 글을 통해서 billing을 2개월 동안 오픈했는데, 사람들이 구매하자마자 계속해서 환불을 했다.

이게 우연의 일치겠지라고 생각을 했는데, 대략 6명 정도가 그랬고, 내 돈이 하늘나라로 날아가는 것을 봐야만 했다.

그래서 코드에 문제가 없는게 아닐까 의심을 하기 시작했다.

 onPurchaseUpdate에서 handlePurchase(purchase) 로직을 처리해야 했다.

override fun onPurchasesUpdated(billingResult: BillingResult?, purchases: MutableList<Purchase>?) {
        if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
            for (purchase in purchases) {
                sharedPrefUtils.putStringSharedPref(activity, REMOVE_AD, "on")
                handlePurchase(purchase)
            }
        } else if (billingResult?.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            // Handle an error caused by a user cancelling the purchase flow.
        } else {
            // Handle any other error codes.
        }
    }

 물건을 구매했으며, 소비해야지. 물건을 consume해야하는 로직이 필요했다.

이 로직이 없으니까, 물건을 구매하고 나고, 5분 후에 환불이 되는 것을 발견했고, 이 로직을 추가하고 난 후에 환불이 되지 않는 것을 알았다...

private fun handlePurchase(purchase: Purchase) {
        val consumeParams = ConsumeParams
                .newBuilder()
                .setPurchaseToken(purchase.purchaseToken)
                .setDeveloperPayload(purchase.developerPayload)
                .build()

        billingClient.consumeAsync(consumeParams) { billingResult, purchaseToken -> }
    }

 그러면, 환불 했으면, SharePrefrence로 지정되어 있는 값은 안 바뀌는데... 내 물건 구매하고 있는 지 확인해봐야하는 거 아닐까?

2. 구매 후, 사용자가 환불 했는 지 알고 싶다.

 환불이 되고 난 후, 로직을 생각해본 적이 없다. 팀원분이 환불이 되고 난 후, 로직을 생각해야 한다고 알려주셨고, billingClient를 만들어서, queryPurchaseHistory를 확인해 보고, 구매 내역이 1개라도 있으면, 광고 제거를 안 시키는 로직으로 생각을 하고 있다. 이게 잘 될지 모르겠지만, 우선 이렇게 해야겠다.

fun getPurchaseHistory() {
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingServiceDisconnected() {

            }

            override fun onBillingSetupFinished(billingResult: BillingResult?) {
                billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP) { billingResult, purchaseHistoryRecordList ->
                    if(billingResult.responseCode == BillingClient.BillingResponseCode.OK){
                        if(purchaseHistoryRecordList != null && purchaseHistoryRecordList.size>0){
                            sharedPrefUtils.putStringSharedPref(activity, REMOVE_AD, "on")
                        }else{
                            sharedPrefUtils.putStringSharedPref(activity, REMOVE_AD, "off")
                        }
                    }
                }
            }
        })
    }

3. BillingManager를 큰 덩어리로 다시 봐보자.

 예전에 BillingManager를 Sample로 제공해줬던 것 같은데... v3로 올라가고는 그러지 않는 것 같다. 가장 심플하게 광고제거를 사용하고 있는 BillingManager는 아래와 같다.

BillingManager 로직

package hbs.com.timetablescreen.utils

import android.app.Activity
import com.android.billingclient.api.*
import hbs.com.timetablescreen.utils.MainUtils.REMOVE_AD

class BillingManager(val activity: Activity) : PurchasesUpdatedListener {
    private val sharedPrefUtils = SharedPrefUtils()
    val billingClient = BillingClient.newBuilder(activity).enablePendingPurchases().setListener(this).build()

    fun makeBillingClient(): BillingClient = BillingClient.newBuilder(activity).enablePendingPurchases().setListener(this).build()

    fun getPurchaseHistory() {
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingServiceDisconnected() {

            }

            override fun onBillingSetupFinished(billingResult: BillingResult?) {
                billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP) { billingResult, purchaseHistoryRecordList ->
                    if(billingResult.responseCode == BillingClient.BillingResponseCode.OK){
                        if(purchaseHistoryRecordList != null && purchaseHistoryRecordList.size>0){
                            sharedPrefUtils.putStringSharedPref(activity, REMOVE_AD, "on")
                        }else{
                            sharedPrefUtils.putStringSharedPref(activity, REMOVE_AD, "off")
                        }
                    }
                }
            }
        })
    }

    fun processToPurchase() {
        val skuList = ArrayList<String>()
        skuList.add(REMOVE_AD)
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingServiceDisconnected() {
            }

            override fun onBillingSetupFinished(billingResult: BillingResult?) {
                val params = SkuDetailsParams.newBuilder()
                params.setSkusList(skuList)
                params.setType(BillingClient.SkuType.INAPP)
                launchBillingFlow(params)
            }
        })
        // Retrieve a value for "skuDetails" by calling querySkuDetailsAsync().
    }

    override fun onPurchasesUpdated(billingResult: BillingResult?, purchases: MutableList<Purchase>?) {
        if (billingResult?.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
            for (purchase in purchases) {
                sharedPrefUtils.putStringSharedPref(activity, REMOVE_AD, "on")
                handlePurchase(purchase)
            }
        } else if (billingResult?.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            // Handle an error caused by a user cancelling the purchase flow.
        } else {
            // Handle any other error codes.
        }
    }

    private fun launchBillingFlow(params: SkuDetailsParams.Builder) {
        billingClient.querySkuDetailsAsync(params.build()) { billingResult, skuDetailsList ->
            // Process the result.
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                if (skuDetailsList.size > 0) {
                    val flowParams = BillingFlowParams.newBuilder()
                            .setSkuDetails(skuDetailsList[0])
                            .build()
                    val responseCode = billingClient.launchBillingFlow(activity, flowParams)
                }
            }
        }
    }

    private fun handlePurchase(purchase: Purchase) {
        val consumeParams = ConsumeParams
                .newBuilder()
                .setPurchaseToken(purchase.purchaseToken)
                .setDeveloperPayload(purchase.developerPayload)
                .build()

        billingClient.consumeAsync(consumeParams) { billingResult, purchaseToken -> }
    }
}

 Activity 로직

 if (id == R.id.nav_purchase_in_app) {
                billingManager.processToPurchase();
}