Created
December 2, 2020 10:50
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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