Reputation: 557
I developed a simple app without any backend cloud server, It has an IN-APP-Billing system for premium users. (It is a one-time purchase). To perform that system, I'm using Google's in-app billing library. When the user purchases, I made a preference value to true, that's how my users can access to the premium options of my app.
here's how I implemented it,
@AndroidEntryPoint
class MainActivity2 : ComponentActivity(), PurchasesUpdatedListener {
@Inject
lateinit var userPreferencesRepository: UserPreferencesRepository
private lateinit var billingClient: BillingClient
private val productId = "premium_features"
@OptIn(ExperimentalPermissionsApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
billingClient = BillingClient.newBuilder(this)
.setListener(this)
.enablePendingPurchases()
.build()
startBillingConnection() // If I not call this method, purchase system is not working
setContent {
MainApp(
onUpgradeSubmitClick = {
launchPurchaseFlow()
}
)
}
}
private fun startBillingConnection() {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Toast.makeText(this@MainActivity2, "Billing Client Ready", Toast.LENGTH_SHORT).show()
// I never see this toast message
}
}
override fun onBillingServiceDisconnected() {
Toast.makeText(this@MainActivity2, "Billing Service Disconnected", Toast.LENGTH_SHORT).show()
// I never see this toast message
}
})
}
private fun launchPurchaseFlow() {
val params = QueryProductDetailsParams.newBuilder()
.setProductList(
listOf(
QueryProductDetailsParams.Product.newBuilder()
.setProductId(productId)
.setProductType(BillingClient.ProductType.INAPP)
.build()
)
)
.build()
billingClient.queryProductDetailsAsync(params) { billingResult, productDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && productDetailsList.isNotEmpty()) {
val productDetails = productDetailsList[0]
val purchaseParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(
listOf(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.build()
)
)
.build()
billingClient.launchBillingFlow(this, purchaseParams)
} else {
Toast.makeText(this, "Premium is not available", Toast.LENGTH_SHORT).show()
}
}
}
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
handlePurchase(purchase)
}
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
Toast.makeText(this, "Purchase canceled!", Toast.LENGTH_SHORT).show()
lifecycleScope.launch{
userPreferencesRepository.changePremiumStatus(false)
}
}
else if (billingResult.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED){
Toast.makeText(this, "Welcome back to Premium!", Toast.LENGTH_SHORT).show()
lifecycleScope.launch{
userPreferencesRepository.changePremiumStatus(true)
}
}
else {
Toast.makeText(this, "Purchase failed!", Toast.LENGTH_SHORT).show()
lifecycleScope.launch{
userPreferencesRepository.changePremiumStatus(false)
}
}
}
private fun handlePurchase(purchase: Purchase) {
Toast.makeText(this, "Welcome to Groovy Premium!", Toast.LENGTH_SHORT).show()
lifecycleScope.launch{
userPreferencesRepository.changePremiumStatus(true)
}
}
override fun onDestroy() {
super.onDestroy()
billingClient.endConnection()
}
}
It is working fine, I already have several users who purchase the premium option of my app. The main issue is, that some users are refunded their purchase amount but I don't build any system to recheck their purchase status and downgrade them. I googled it and searched it over this platform but I cant find any easy-to-implement solution that I implement without any API-based cloud server.
The library I'm using,
implementation("com.android.billingclient:billing:6.0.1")
Is there any system that I implement that can check the users purchase status and downgrade them if they refunded their purchase?
Upvotes: 0
Views: 31
Reputation: 53
You should use queryPurchasesAsync to get a list of previous purchased for this user (code is from one of my apps that has a "no-ads" purchase):
fun queryPurchases() {
if (!billingClient.isReady) {
Log.e(TAG, "queryPurchases: BillingClient is not ready")
}
// Query for existing subscription products that have been purchased.
billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build()) { billingResult, purchaseList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Log.d(TAG, "Purchases: $purchaseList")
purchaseQueryListener?.purchaseQueryResult(purchaseList)
} else {
Log.e(TAG, billingResult.debugMessage)
}
}
}
Then you can process the result & update your local preference to reflect whether or not the purchase is still valid (I do this in a separate fun, but you could just do it in the lambda of queryPurchasesAsync):
override fun purchaseQueryResult(purchases: MutableList<Purchase>) {
Log.d("NOADS (New)", "Purchased: ${purchases.isNotEmpty()}")
savePref(this, "pref_noads", purchases.isNotEmpty())
if (purchases.isEmpty()) {
checkForPaidVersion()
}
bcw.terminateBillingConnection()
}
I run this check in 2 places - when the app first starts in MainActivity onCreate & then again if the user goes to the purchase screen, so that I can disable the purchase button if they've already purchased the upgrade.
Upvotes: 0