Skip to content

Instantly share code, notes, and snippets.

@hitesh-dhamshaniya
Created December 2, 2020 10:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hitesh-dhamshaniya/79add9afede637c3b5c00e493f704a13 to your computer and use it in GitHub Desktop.
Save hitesh-dhamshaniya/79add9afede637c3b5c00e493f704a13 to your computer and use it in GitHub Desktop.
Android Native InAppPurchase Manager, used to implement InApp Purchase in to the android app.
import android.app.Activity
import android.content.Context
import android.util.Log
import com.android.billingclient.api.AcknowledgePurchaseParams
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.BillingResult
import com.android.billingclient.api.Purchase
import com.android.billingclient.api.PurchasesUpdatedListener
import com.android.billingclient.api.SkuDetails
import com.android.billingclient.api.SkuDetailsParams
import com.orhanobut.logger.Logger
import java.util.Calendar
import java.util.Collections
/**
* Based on version implementation 'com.android.billingclient:billing:3.0.0'
* @version 1.0
**/
class InAppPurchaseManager(private val context: Context) : PurchasesUpdatedListener {
companion object {
//TODO: Replace with actual SKU
//"single_user_monthly", "single_user_half_yearly", "single_user_yearly"
private const val SKU = "single_user_yearly"
private const val TAG = "InAppPurchase"
}
private lateinit var billingClient: BillingClient
private var mSkuListener: ((SkuDetails) -> Unit)? = null
private var mPurchaseUpdate: ((Purchase, SkuDetails) -> Unit)? = null
private var mBillingHistory: ((MutableList<Purchase>, SkuDetails) -> Unit)? = null
private var mErrorListener: ((String) -> Unit)? = null
private var mSkuDetails: SkuDetails? = null
fun setSkuListener(listener: (SkuDetails) -> Unit) {
mSkuListener = listener
}
fun setBillingHistory(listener: (MutableList<Purchase>, SkuDetails) -> Unit) {
mBillingHistory = listener
}
fun setPurchaseUpdate(listener: (Purchase, SkuDetails) -> Unit) {
mPurchaseUpdate = listener
}
fun setErrorListener(listener: (String) -> Unit) {
mErrorListener = listener
}
/**
* To setup billing client, after initial manager class need to setup BillingClient
*/
fun setupBillingClient() {
billingClient = BillingClient.newBuilder(context)
.enablePendingPurchases()
.setListener(this)
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
log("Setup Billing Done")
loadAllSKUs()
} else {
mErrorListener?.invoke("Setup Failed:" + billingResult.responseCode)
}
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
log("onBillingServiceDisconnected")
}
})
}
/**
* Fetch added product from store
*/
private fun loadAllSKUs() {
if (billingClient.isReady) {
val params = SkuDetailsParams
.newBuilder()
.setSkusList(listOf(SKU))
.setType(BillingClient.SkuType.SUBS)
.build()
billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList ->
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
if (!skuDetailsList.isNullOrEmpty()) {
val skuDetail = skuDetailsList.first()
log("sku:$skuDetail")
mSkuDetails = skuDetail
mSkuListener?.invoke(skuDetail)
} else {
mErrorListener?.invoke("No SKU found")
}
} else {
mErrorListener?.invoke("Fetch SKU Failed:" + billingResult.responseCode)
}
}
} else {
log("Billing Client not ready")
}
}
/**
* Method used to get history of purchased item
*/
fun getBillingHistory() {
val purchasesList = billingClient.queryPurchases(BillingClient.SkuType.SUBS).purchasesList
purchasesList?.forEach { purchase ->
log(purchase.orderId + "," + purchase.sku + "," + purchase.originalJson)
}
mSkuDetails?.let {
mBillingHistory?.invoke(purchasesList ?: Collections.emptyList(), it)
}
}
/**
* makePurchase
* @param activity : Activity
*/
fun makePurchase(activity: Activity) {
if (mSkuDetails == null) return
val billingFlowParams = BillingFlowParams
.newBuilder()
.setSkuDetails(mSkuDetails!!)
.build()
billingClient.launchBillingFlow(activity, billingFlowParams)
}
/**
* Override method onPurchasesUpdated, provide billing result and purchased item list
*/
override fun onPurchasesUpdated(
billingResult: BillingResult,
purchases: MutableList<Purchase>?
) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
//if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {}
log("State:" + purchase.purchaseState)
log(purchase.orderId + "," + purchase.sku + "," + purchase.originalJson)
log("ExpiryTime:" + getExpiryTime(purchase.purchaseTime, mSkuDetails!!.subscriptionPeriod))
log("acknowledgePurchase:" + purchase.purchaseToken)
Log.w(TAG, purchase.originalJson)
mSkuDetails?.let {
mPurchaseUpdate?.invoke(purchase, it)
}
}
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
log("User Cancelled")
log(billingResult.debugMessage.toString())
} else {
log(billingResult.responseCode.toString())
}
}
/**
* Method used to acknowledge purchase
*/
fun acknowledgePurchase(purchaseToken: String) {
val params = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build()
billingClient.acknowledgePurchase(params) { billingResult ->
val responseCode = billingResult.responseCode
val debugMessage = billingResult.debugMessage
log(debugMessage)
log(responseCode.toString())
}
}
private fun log(message: String) {
Logger.w(message)
}
fun getExpiryTime(purchaseTime: Long, period: String): Calendar {
val calendar: Calendar = Calendar.getInstance()
calendar.timeInMillis = purchaseTime
val now = Calendar.getInstance()
while (calendar.before(now)) {
when (period) {
"P1W" -> calendar.add(Calendar.HOUR, 7 * 24)
"P1M" -> calendar.add(Calendar.MONTH, 1)
"P3M" -> calendar.add(Calendar.MONTH, 3)
"P6M" -> calendar.add(Calendar.MONTH, 6)
"P1Y" -> calendar.add(Calendar.YEAR, 1)
}
}
return calendar
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment