From df682e0b16fcacf050c7142452852f76ae9eef0d Mon Sep 17 00:00:00 2001 From: haripriyan Date: Fri, 13 Oct 2023 16:19:49 +0530 Subject: [PATCH 01/11] Upgrades to Billing library 5.2.1 --- chargebee/build.gradle | 7 +- .../java/com/chargebee/android/Chargebee.kt | 2 +- .../billingservice/BillingClientManager.kt | 176 ++++++++++++------ .../com/chargebee/android/models/Products.kt | 6 +- 4 files changed, 128 insertions(+), 63 deletions(-) diff --git a/chargebee/build.gradle b/chargebee/build.gradle index 838961e..82ac646 100644 --- a/chargebee/build.gradle +++ b/chargebee/build.gradle @@ -1,13 +1,12 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { minSdkVersion 21 - targetSdkVersion 30 + targetSdkVersion 31 versionCode 1 versionName "1.1.0" @@ -42,7 +41,7 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' // Google play billing library - implementation 'com.android.billingclient:billing-ktx:4.0.0' + implementation 'com.android.billingclient:billing-ktx:5.2.1' // AssertJ testImplementation "org.assertj:assertj-core:$assertj_version" diff --git a/chargebee/src/main/java/com/chargebee/android/Chargebee.kt b/chargebee/src/main/java/com/chargebee/android/Chargebee.kt index 5ecf590..763ee1f 100644 --- a/chargebee/src/main/java/com/chargebee/android/Chargebee.kt +++ b/chargebee/src/main/java/com/chargebee/android/Chargebee.kt @@ -35,7 +35,7 @@ object Chargebee { var appName: String = "Chargebee" var environment: String = "cb_android_sdk" const val platform: String = "Android" - const val sdkVersion: String = BuildConfig.VERSION_NAME + const val sdkVersion: String = "1.2.0" const val limit: String = "100" private const val PLAY_STORE_SUBSCRIPTION_URL = "https://play.google.com/store/account/subscriptions" diff --git a/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt b/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt index ce7912b..b2d3666 100644 --- a/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt +++ b/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt @@ -25,7 +25,6 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { var mContext: Context? = context private val handler = Handler(Looper.getMainLooper()) private var purchaseCallBack: CBCallback.PurchaseCallback? = null - private val skusWithSkuDetails = arrayListOf() private val TAG = javaClass.simpleName lateinit var product: CBProduct private lateinit var restorePurchaseCallBack: CBCallback.RestorePurchaseCallback @@ -36,12 +35,12 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { } internal fun retrieveProducts( - skuList: ArrayList, callBack: CBCallback.ListProductsCallback> + products: ArrayList, callBack: CBCallback.ListProductsCallback> ) { val productsList = ArrayList() - retrieveProducts(ProductType.SUBS.value, skuList, { subsProductsList -> + retrieveProducts(BillingClient.ProductType.SUBS, products, { subsProductsList -> productsList.addAll(subsProductsList) - retrieveProducts(ProductType.INAPP.value, skuList, { inAppProductsList -> + retrieveProducts(BillingClient.ProductType.INAPP, products, { inAppProductsList -> productsList.addAll(inAppProductsList) callBack.onSuccess(productsList) }, { error -> @@ -53,13 +52,13 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { } internal fun retrieveProducts( - @BillingClient.SkuType skuType: String, - skuList: ArrayList, response: (ArrayList) -> Unit, + @BillingClient.ProductType productType: String, + products: ArrayList, response: (ArrayList) -> Unit, errorDetail: (CBException) -> Unit ) { onConnected({ status -> if (status) - loadProductDetails(skuType, skuList, { + loadProductDetails(productType, products, { response(it) }, { errorDetail(it) @@ -72,39 +71,73 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { errorDetail(error) }) } + /* Get the SKU/Products from Play Console */ private fun loadProductDetails( - @BillingClient.SkuType skuType: String, - skuList: ArrayList, + @BillingClient.ProductType productType: String, + products: ArrayList, response: (ArrayList) -> Unit, errorDetail: (CBException) -> Unit ) { try { - val params = SkuDetailsParams - .newBuilder() - .setSkusList(skuList) - .setType(skuType) - .build() - billingClient?.querySkuDetailsAsync( - params - ) { billingResult, skuDetailsList -> - if (billingResult.responseCode == OK && skuDetailsList != null) { + val queryProductDetails = products.map { + QueryProductDetailsParams.Product.newBuilder() + .setProductId(it) + .setProductType(productType) + .build() + } + + val productDetailsParams = QueryProductDetailsParams.newBuilder().setProductList(queryProductDetails).build() + + billingClient?.queryProductDetailsAsync( + productDetailsParams + ) { billingResult, productsDetail -> + if (billingResult.responseCode == OK && productsDetail != null) { try { - skusWithSkuDetails.clear() - for (skuProduct in skuDetailsList) { - val product = CBProduct( - skuProduct.sku, - skuProduct.title, - skuProduct.price, - skuProduct, - false, - ProductType.getProductType(skuProduct.type) - ) - skusWithSkuDetails.add(product) + val cbProductDetails = arrayListOf() + for (productDetail in productsDetail) { + val productId = productDetail.productId + val productType = ProductType.getProductType(productDetail.productType) + val productTitle = productDetail.title + + val subscriptionOfferDetails = productDetail.subscriptionOfferDetails + subscriptionOfferDetails?.forEach { + val price = it.pricingPhases?.pricingPhaseList?.first()?.formattedPrice ?: "0" + val offerToken = it.offerToken + val basePlanId = it.basePlanId + + val product = CBProduct( + productId, + productTitle, + basePlanId, + price, + productDetail, + offerToken, + false, + productType + ) + cbProductDetails.add(product) + } + + val oneTimePurchaseOfferDetails = productDetail.oneTimePurchaseOfferDetails + oneTimePurchaseOfferDetails?.let { + val price = it.formattedPrice + val product = CBProduct( + productId, + productTitle, + null, + price, + productDetail, + null, + false, + productType + ) + cbProductDetails.add(product) + } } - Log.i(TAG, "Product details :$skusWithSkuDetails") - response(skusWithSkuDetails) + Log.i(TAG, "Product details :$cbProductDetails") + response(cbProductDetails) } catch (ex: CBException) { errorDetail( CBException( @@ -135,9 +168,9 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { ) { this.purchaseCallBack = purchaseCallBack onConnected({ status -> - if (status) + if (status) { purchase(product) - else + } else purchaseCallBack.onError( connectionError ) @@ -146,16 +179,29 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { }) } + /* Purchase the product: Initiates the billing flow for an In-app-purchase */ private fun purchase(product: CBProduct) { this.product = product - val skuDetails = product.skuDetails + val productDetails = product.productDetails + val offerToken = product.offerToken + + val productDetailsParamsList = + listOf( + offerToken?.let { + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(productDetails) + .setOfferToken(it) + .build() + } + ) - val params = BillingFlowParams.newBuilder() - .setSkuDetails(skuDetails) - .build() + val billingFlowParams = + BillingFlowParams.newBuilder() + .setProductDetailsParamsList(productDetailsParamsList) + .build() - billingClient?.launchBillingFlow(mContext as Activity, params) + billingClient?.launchBillingFlow(mContext as Activity, billingFlowParams) .takeIf { billingResult -> billingResult?.responseCode != OK }?.let { billingResult -> @@ -166,7 +212,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { httpStatusCode = billingResult.responseCode ) ) - if (product.skuDetails.type == ProductType.SUBS.value) { + if (ProductType.SUBS == product.productType) { purchaseCallBack?.onError( billingError ) @@ -202,6 +248,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { OK -> { return true } + FEATURE_NOT_SUPPORTED -> { return false } @@ -229,6 +276,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { Purchase.PurchaseState.PURCHASED -> { acknowledgePurchase(purchase) } + Purchase.PurchaseState.PENDING -> { purchaseCallBack?.onError( CBException( @@ -239,6 +287,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { ) ) } + Purchase.PurchaseState.UNSPECIFIED_STATE -> { purchaseCallBack?.onError( CBException( @@ -252,29 +301,31 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { } } } - else -> { - if (product.skuDetails.type == ProductType.SUBS.value) - purchaseCallBack?.onError( - throwCBException(billingResult) - ) - else - oneTimePurchaseCallback?.onError( - throwCBException(billingResult) - ) - } + + else -> { + if (product.productType == ProductType.SUBS) + purchaseCallBack?.onError( + throwCBException(billingResult) + ) + else + oneTimePurchaseCallback?.onError( + throwCBException(billingResult) + ) + } } } /* Acknowledge the Purchases */ private fun acknowledgePurchase(purchase: Purchase) { - when(product.productType){ - ProductType.SUBS -> { - isAcknowledgedPurchase(purchase,{ + when (product.productType) { + ProductType.SUBS -> { + isAcknowledgedPurchase(purchase, { validateReceipt(purchase.purchaseToken, product) }, { purchaseCallBack?.onError(it) }) } + ProductType.INAPP -> { if (CBPurchase.productType == OneTimeProductType.CONSUMABLE) { consumeAsyncPurchase(purchase.purchaseToken) @@ -289,7 +340,11 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { } } - private fun isAcknowledgedPurchase(purchase: Purchase, success: () -> Unit, error: (CBException) -> Unit){ + private fun isAcknowledgedPurchase( + purchase: Purchase, + success: () -> Unit, + error: (CBException) -> Unit + ) { if (!purchase.isAcknowledged) { val params = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.purchaseToken) @@ -312,6 +367,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { ) } } + else -> { error( throwCBException(billingResult) @@ -325,10 +381,11 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { /* Consume the Purchases */ private fun consumeAsyncPurchase(token: String) { consumePurchase(token) { billingResult, purchaseToken -> - when(billingResult.responseCode){ + when (billingResult.responseCode) { OK -> { validateNonSubscriptionReceipt(purchaseToken, product) } + else -> { oneTimePurchaseCallback?.onError( throwCBException(billingResult) @@ -379,6 +436,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { purchaseCallBack?.onError(CBException(ErrorDetail(message = GPErrorCode.PurchaseInvalid.errorMsg))) } } + is ChargebeeResult.Error -> { Log.e(TAG, "Exception from Server - validateReceipt() : ${it.exp.message}") purchaseCallBack?.onError(it.exp) @@ -459,7 +517,8 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { private fun queryPurchaseHistoryAsync( productType: String, purchaseTransactionList: (List?) -> Unit ) { - billingClient?.queryPurchaseHistoryAsync(productType) { billingResult, subsHistoryList -> + val queryPurchaseHistoryParams = QueryPurchaseHistoryParams.newBuilder().setProductType(productType).build() + billingClient?.queryPurchaseHistoryAsync(queryPurchaseHistoryParams) { billingResult, subsHistoryList -> if (billingResult.responseCode == OK) { val purchaseHistoryList = subsHistoryList?.map { it.toPurchaseTransaction(productType) @@ -517,6 +576,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { Log.i(TAG, "Google Billing Setup Done!") status(true) } + else -> { connectionError(throwCBException(billingResult)) } @@ -590,8 +650,12 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { oneTimePurchaseCallback?.onError(CBException(ErrorDetail(message = GPErrorCode.PurchaseInvalid.errorMsg))) } } + is ChargebeeResult.Error -> { - Log.e(TAG, "Exception from Server - validateNonSubscriptionReceipt() : ${it.exp.message}") + Log.e( + TAG, + "Exception from Server - validateNonSubscriptionReceipt() : ${it.exp.message}" + ) oneTimePurchaseCallback?.onError(it.exp) } } diff --git a/chargebee/src/main/java/com/chargebee/android/models/Products.kt b/chargebee/src/main/java/com/chargebee/android/models/Products.kt index 88e63a8..ad9c608 100644 --- a/chargebee/src/main/java/com/chargebee/android/models/Products.kt +++ b/chargebee/src/main/java/com/chargebee/android/models/Products.kt @@ -1,13 +1,15 @@ package com.chargebee.android.models -import com.android.billingclient.api.SkuDetails +import com.android.billingclient.api.ProductDetails import com.chargebee.android.billingservice.ProductType data class CBProduct( val productId: String, val productTitle: String, + val productBasePlanId: String?, val productPrice: String, - var skuDetails: SkuDetails, + var productDetails: ProductDetails, + var offerToken: String?, var subStatus: Boolean, var productType: ProductType ) \ No newline at end of file From 0d73e1d35f8269c56b912500a14f749ad0dec239 Mon Sep 17 00:00:00 2001 From: haripriyan Date: Mon, 16 Oct 2023 12:59:29 +0530 Subject: [PATCH 02/11] Fixes unit tests --- chargebee/build.gradle | 4 +- .../java/com/chargebee/android/Chargebee.kt | 2 +- .../billingservice/BillingClientManager.kt | 77 +++++++++++-------- .../billingclient/api/StubProductDetails.kt | 38 +++++++++ .../BillingClientManagerTest.kt | 5 +- .../android/restore/RestorePurchaseTest.kt | 8 +- 6 files changed, 96 insertions(+), 38 deletions(-) create mode 100644 chargebee/src/test/java/com/android/billingclient/api/StubProductDetails.kt diff --git a/chargebee/build.gradle b/chargebee/build.gradle index 82ac646..d216def 100644 --- a/chargebee/build.gradle +++ b/chargebee/build.gradle @@ -8,7 +8,7 @@ android { minSdkVersion 21 targetSdkVersion 31 versionCode 1 - versionName "1.1.0" + versionName "2.0.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -52,6 +52,8 @@ dependencies { // Mockito testImplementation 'org.mockito:mockito-core:2.23.0' + testImplementation 'org.json:json:20140107' + testImplementation 'androidx.test:core:1.2.0' testImplementation 'androidx.test.ext:junit:1.1.1' diff --git a/chargebee/src/main/java/com/chargebee/android/Chargebee.kt b/chargebee/src/main/java/com/chargebee/android/Chargebee.kt index 763ee1f..785de24 100644 --- a/chargebee/src/main/java/com/chargebee/android/Chargebee.kt +++ b/chargebee/src/main/java/com/chargebee/android/Chargebee.kt @@ -35,7 +35,7 @@ object Chargebee { var appName: String = "Chargebee" var environment: String = "cb_android_sdk" const val platform: String = "Android" - const val sdkVersion: String = "1.2.0" + const val sdkVersion: String = "2.0.0" const val limit: String = "100" private const val PLAY_STORE_SUBSCRIPTION_URL = "https://play.google.com/store/account/subscriptions" diff --git a/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt b/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt index b2d3666..f9c4107 100644 --- a/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt +++ b/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt @@ -9,14 +9,13 @@ import com.android.billingclient.api.* import com.android.billingclient.api.BillingClient.BillingResponseCode.* import com.chargebee.android.ErrorDetail import com.chargebee.android.billingservice.BillingErrorCode.Companion.throwCBException -import com.chargebee.android.models.PurchaseTransaction import com.chargebee.android.exceptions.CBException import com.chargebee.android.exceptions.ChargebeeResult import com.chargebee.android.models.CBNonSubscriptionResponse import com.chargebee.android.models.CBProduct +import com.chargebee.android.models.PurchaseTransaction import com.chargebee.android.network.CBReceiptResponse import com.chargebee.android.restore.CBRestorePurchaseManager -import kotlin.collections.ArrayList class BillingClientManager(context: Context) : PurchasesUpdatedListener { @@ -97,42 +96,16 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { try { val cbProductDetails = arrayListOf() for (productDetail in productsDetail) { - val productId = productDetail.productId - val productType = ProductType.getProductType(productDetail.productType) - val productTitle = productDetail.title val subscriptionOfferDetails = productDetail.subscriptionOfferDetails subscriptionOfferDetails?.forEach { - val price = it.pricingPhases?.pricingPhaseList?.first()?.formattedPrice ?: "0" - val offerToken = it.offerToken - val basePlanId = it.basePlanId - - val product = CBProduct( - productId, - productTitle, - basePlanId, - price, - productDetail, - offerToken, - false, - productType - ) + val product = subscriptionCbProduct(productDetail, it) cbProductDetails.add(product) } val oneTimePurchaseOfferDetails = productDetail.oneTimePurchaseOfferDetails oneTimePurchaseOfferDetails?.let { - val price = it.formattedPrice - val product = CBProduct( - productId, - productTitle, - null, - price, - productDetail, - null, - false, - productType - ) + val product = oneTimeCbProduct(productDetail, it) cbProductDetails.add(product) } } @@ -162,6 +135,49 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { } } + private fun oneTimeCbProduct( + productDetail: ProductDetails, + it: ProductDetails.OneTimePurchaseOfferDetails + ): CBProduct { + val productId = productDetail.productId + val productType = ProductType.getProductType(productDetail.productType) + val productTitle = productDetail.title + val price = it.formattedPrice + return CBProduct( + productId, + productTitle, + null, + price, + productDetail, + null, + false, + productType + ) + } + + private fun subscriptionCbProduct( + productDetail: ProductDetails, + it: ProductDetails.SubscriptionOfferDetails + ): CBProduct { + val productId = productDetail.productId + val productType = ProductType.getProductType(productDetail.productType) + val productTitle = productDetail.title + val price = it.pricingPhases?.pricingPhaseList?.first()?.formattedPrice ?: "0" + val offerToken = it.offerToken + val basePlanId = it.basePlanId + + return CBProduct( + productId, + productTitle, + basePlanId, + price, + productDetail, + offerToken, + false, + productType + ) + } + internal fun purchase( product: CBProduct, purchaseCallBack: CBCallback.PurchaseCallback @@ -679,7 +695,6 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { } ?: run { completionCallback.onError(itemNotOwnedException()) } - } else completionCallback.onError( connectionError diff --git a/chargebee/src/test/java/com/android/billingclient/api/StubProductDetails.kt b/chargebee/src/test/java/com/android/billingclient/api/StubProductDetails.kt new file mode 100644 index 0000000..9bff30c --- /dev/null +++ b/chargebee/src/test/java/com/android/billingclient/api/StubProductDetails.kt @@ -0,0 +1,38 @@ +package com.android.billingclient.api + +import kotlin.reflect.KClass + +fun KClass.create(): ProductDetails { + val productDetails = ProductDetails("{\n" + + "\t\"productId\": \"gold\",\n" + + "\t\"type\": \"subs\",\n" + + "\t\"title\": \"Gold Plan (com.chargebee.newsample (unreviewed))\",\n" + + "\t\"name\": \"Gold Plan\",\n" + + "\t\"localizedIn\": [\"en-US\"],\n" + + "\t\"skuDetailsToken\": \"AEuhp4Ln65Xw7Do9yIO-o4XIj7rAvn_FD90WWajD79kzt0GiNuNm2ACU15T3q56qZTs=\",\n" + + "\t\"subscriptionOfferDetails\": [{\n" + + "\t\t\"offerIdToken\": \"AUj\\/YhiCrZ\\/NvaFihCC8rjAWfXEsLtf\\/qPgutEo1M04GIc8psPY06GcWBpun6qf\\/NhMXcQe3KmD+rbgud2XiLO3ptF41\\/HWcHR7YfYcU7brJ6mM=\",\n" + + "\t\t\"basePlanId\": \"weekly\",\n" + + "\t\t\"pricingPhases\": [{\n" + + "\t\t\t\"priceAmountMicros\": 20000000,\n" + + "\t\t\t\"priceCurrencyCode\": \"INR\",\n" + + "\t\t\t\"formattedPrice\": \"₹20.00\",\n" + + "\t\t\t\"billingPeriod\": \"P1W\",\n" + + "\t\t\t\"recurrenceMode\": 1\n" + + "\t\t}],\n" + + "\t\t\"offerTags\": []\n" + + "\t}, {\n" + + "\t\t\"offerIdToken\": \"AUj\\/YhgOwTW\\/BAGR2Po8uAsNJc6G+Z5xSDRBnDU7VJ5GN21yhMvuUjUMFDNCwEu+GtDaN2CzYoLqu7wHu\\/T+37S1KlyLFi0tfSAZcJE5MisuY+hKUuRJ\",\n" + + "\t\t\"basePlanId\": \"monthly\",\n" + + "\t\t\"pricingPhases\": [{\n" + + "\t\t\t\"priceAmountMicros\": 40000000,\n" + + "\t\t\t\"priceCurrencyCode\": \"INR\",\n" + + "\t\t\t\"formattedPrice\": \"₹40.00\",\n" + + "\t\t\t\"billingPeriod\": \"P1M\",\n" + + "\t\t\t\"recurrenceMode\": 1\n" + + "\t\t}],\n" + + "\t\t\"offerTags\": []\n" + + "\t}]\n" + + "}") + return productDetails +} \ No newline at end of file diff --git a/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt b/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt index 1511cbe..5befe09 100644 --- a/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt +++ b/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt @@ -61,8 +61,9 @@ class BillingClientManagerTest { private val receiptDetail = ReceiptDetail("subscriptionId", "customerId", "planId") private var callBackOneTimePurchase: CBCallback.OneTimePurchaseCallback? = null private val nonSubscriptionDetail = NonSubscription("invoiceId", "customerId", "chargeId") - private val otpProducts = CBProduct("test.consumable","Example product","100.0", SkuDetails(""),true, productType = ProductType.INAPP) - private val subProducts = CBProduct("chargebee.premium.android","Premium Plan","", SkuDetails(""),true, ProductType.SUBS) + private val productDetails = ProductDetails::class.create() + private val otpProducts = CBProduct("test.consumable","Example product","basePlanId", "100.0", productDetails,"offerToken", true, productType = ProductType.INAPP) + private val subProducts = CBProduct("chargebee.premium.android","Premium Plan","basePlanId", "", productDetails,"offerToken", true, ProductType.SUBS) @Before fun setUp() { diff --git a/chargebee/src/test/java/com/chargebee/android/restore/RestorePurchaseTest.kt b/chargebee/src/test/java/com/chargebee/android/restore/RestorePurchaseTest.kt index 83c71f9..38a6c60 100644 --- a/chargebee/src/test/java/com/chargebee/android/restore/RestorePurchaseTest.kt +++ b/chargebee/src/test/java/com/chargebee/android/restore/RestorePurchaseTest.kt @@ -4,8 +4,6 @@ import com.android.billingclient.api.* import com.chargebee.android.Chargebee import com.chargebee.android.ErrorDetail import com.chargebee.android.billingservice.CBCallback.RestorePurchaseCallback -import com.chargebee.android.billingservice.OneTimeProductType -import com.chargebee.android.billingservice.ProductType import com.chargebee.android.exceptions.CBException import com.chargebee.android.exceptions.ChargebeeResult import com.chargebee.android.models.* @@ -21,6 +19,7 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any import org.mockito.Mockito import org.mockito.MockitoAnnotations import org.mockito.junit.MockitoJUnitRunner @@ -72,6 +71,9 @@ class RestorePurchaseTest { CBRestorePurchaseManager.fetchStoreSubscriptionStatus( purchaseTransaction, + purchaseTransaction, + purchaseTransaction, + arrayListOf(), completionCallback = object : RestorePurchaseCallback { override fun onSuccess(result: List) { lock.countDown() @@ -109,7 +111,7 @@ class RestorePurchaseTest { Matchers.instanceOf(CBException::class.java) ) Mockito.verify(CBRestorePurchaseManager, Mockito.times(1)) - .getRestorePurchases(purchaseTransaction) + .getRestorePurchases(any(), any(), any(), any()) }) } lock.await() From 4cf01e0983f0d59c3991affb84f2adc0eae38e10 Mon Sep 17 00:00:00 2001 From: haripriyan Date: Tue, 17 Oct 2023 17:19:11 +0530 Subject: [PATCH 03/11] updates example app --- app/build.gradle | 9 ++++----- app/src/main/AndroidManifest.xml | 13 +++++++------ .../main/java/com/chargebee/example/MainActivity.kt | 1 - .../example/adapter/ProductListAdapter.java | 2 +- .../chargebee/example/billing/BillingActivity.java | 7 +++++++ .../com/chargebee/example/items/ItemActivity.kt | 2 -- .../com/chargebee/example/items/ItemsActivity.kt | 3 --- .../chargebee/example/plan/PlanInJavaActivity.java | 4 ---- .../com/chargebee/example/plan/PlansActivity.kt | 3 --- .../example/subscription/SubscriptionActivity.kt | 3 +-- .../com/chargebee/example/token/TokenViewModel.kt | 1 - .../com/chargebee/example/token/TokenizeActivity.kt | 1 - build.gradle | 4 ++-- chargebee/build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 15 files changed, 25 insertions(+), 34 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d1613a9..887970e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,10 +1,9 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { - compileSdkVersion 30 + compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -12,8 +11,8 @@ android { defaultConfig { applicationId "com.chargebee.example" minSdkVersion 21 - targetSdkVersion 30 - versionCode 3 + targetSdkVersion 33 + versionCode 6 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -41,7 +40,7 @@ dependencies { implementation 'com.google.android.material:material:1.1.0' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' implementation 'com.google.code.gson:gson:2.8.8' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 89f51ce..0ff6c0a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,12 +17,13 @@ - - - - - - + + + + + + diff --git a/app/src/main/java/com/chargebee/example/MainActivity.kt b/app/src/main/java/com/chargebee/example/MainActivity.kt index b158d4d..5520c6c 100644 --- a/app/src/main/java/com/chargebee/example/MainActivity.kt +++ b/app/src/main/java/com/chargebee/example/MainActivity.kt @@ -16,7 +16,6 @@ import androidx.recyclerview.widget.RecyclerView import com.chargebee.android.Chargebee import com.chargebee.android.billingservice.CBCallback import com.chargebee.android.billingservice.CBPurchase -import com.chargebee.android.models.CBRestoreSubscription import com.chargebee.android.exceptions.CBException import com.chargebee.android.models.CBProduct import com.chargebee.example.adapter.ListItemsAdapter diff --git a/app/src/main/java/com/chargebee/example/adapter/ProductListAdapter.java b/app/src/main/java/com/chargebee/example/adapter/ProductListAdapter.java index 4bba7c2..05d06f4 100644 --- a/app/src/main/java/com/chargebee/example/adapter/ProductListAdapter.java +++ b/app/src/main/java/com/chargebee/example/adapter/ProductListAdapter.java @@ -32,7 +32,7 @@ public ProductListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int vi @Override public void onBindViewHolder(ProductListAdapter.ViewHolder holder, int position) { CBProduct products = mProductsList.get(position); - holder.mTextViewTitle.setText(products.getProductId()); + holder.mTextViewTitle.setText(products.getProductId() + products.getProductBasePlanId()); holder.mTextViewPrice.setText(products.getProductPrice()); if (products.getSubStatus()) { holder.mTextViewSubscribe.setText(R.string.status_subscribed); diff --git a/app/src/main/java/com/chargebee/example/billing/BillingActivity.java b/app/src/main/java/com/chargebee/example/billing/BillingActivity.java index 753151b..fba9c6d 100644 --- a/app/src/main/java/com/chargebee/example/billing/BillingActivity.java +++ b/app/src/main/java/com/chargebee/example/billing/BillingActivity.java @@ -1,18 +1,25 @@ package com.chargebee.example.billing; import android.app.Dialog; +import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; + +import androidx.annotation.NonNull; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.chargebee.android.ProgressBarListener; +import com.chargebee.android.billingservice.CBCallback; +import com.chargebee.android.billingservice.CBPurchase; import com.chargebee.android.billingservice.OneTimeProductType; import com.chargebee.android.billingservice.ProductType; +import com.chargebee.android.exceptions.CBException; import com.chargebee.android.models.CBProduct; +import com.chargebee.android.models.NonSubscription; import com.chargebee.android.network.CBCustomer; import com.chargebee.example.BaseActivity; import com.chargebee.example.R; diff --git a/app/src/main/java/com/chargebee/example/items/ItemActivity.kt b/app/src/main/java/com/chargebee/example/items/ItemActivity.kt index c015368..afb345f 100644 --- a/app/src/main/java/com/chargebee/example/items/ItemActivity.kt +++ b/app/src/main/java/com/chargebee/example/items/ItemActivity.kt @@ -6,10 +6,8 @@ import android.widget.Button import android.widget.EditText import android.widget.TextView import androidx.lifecycle.Observer -import com.chargebee.android.ErrorDetail import com.chargebee.example.BaseActivity import com.chargebee.example.R -import com.google.gson.Gson class ItemActivity: BaseActivity() { diff --git a/app/src/main/java/com/chargebee/example/items/ItemsActivity.kt b/app/src/main/java/com/chargebee/example/items/ItemsActivity.kt index 921588e..9f1eb21 100644 --- a/app/src/main/java/com/chargebee/example/items/ItemsActivity.kt +++ b/app/src/main/java/com/chargebee/example/items/ItemsActivity.kt @@ -1,7 +1,6 @@ package com.chargebee.example.items import android.os.Bundle -import android.util.Log import android.view.View import android.widget.TextView import androidx.lifecycle.Observer @@ -9,11 +8,9 @@ import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.chargebee.android.Chargebee -import com.chargebee.android.ErrorDetail import com.chargebee.example.BaseActivity import com.chargebee.example.R import com.chargebee.example.adapter.ItemsAdapter -import com.google.gson.Gson class ItemsActivity : BaseActivity(), ItemsAdapter.ItemClickListener { diff --git a/app/src/main/java/com/chargebee/example/plan/PlanInJavaActivity.java b/app/src/main/java/com/chargebee/example/plan/PlanInJavaActivity.java index c53cefe..a211878 100644 --- a/app/src/main/java/com/chargebee/example/plan/PlanInJavaActivity.java +++ b/app/src/main/java/com/chargebee/example/plan/PlanInJavaActivity.java @@ -6,12 +6,8 @@ import android.widget.EditText; import android.widget.TextView; -import com.chargebee.android.ErrorDetail; -import com.chargebee.android.models.Plan; import com.chargebee.example.BaseActivity; import com.chargebee.example.R; -import com.google.gson.Gson; -import com.google.gson.JsonObject; public class PlanInJavaActivity extends BaseActivity { diff --git a/app/src/main/java/com/chargebee/example/plan/PlansActivity.kt b/app/src/main/java/com/chargebee/example/plan/PlansActivity.kt index 4d020bb..7e6890c 100644 --- a/app/src/main/java/com/chargebee/example/plan/PlansActivity.kt +++ b/app/src/main/java/com/chargebee/example/plan/PlansActivity.kt @@ -8,12 +8,9 @@ import androidx.lifecycle.Observer import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.chargebee.android.Chargebee -import com.chargebee.android.ErrorDetail import com.chargebee.example.BaseActivity import com.chargebee.example.R import com.chargebee.example.adapter.ItemsAdapter -import com.google.gson.Gson class PlansActivity : BaseActivity(), ItemsAdapter.ItemClickListener { diff --git a/app/src/main/java/com/chargebee/example/subscription/SubscriptionActivity.kt b/app/src/main/java/com/chargebee/example/subscription/SubscriptionActivity.kt index a8bf45f..f9b1dbb 100644 --- a/app/src/main/java/com/chargebee/example/subscription/SubscriptionActivity.kt +++ b/app/src/main/java/com/chargebee/example/subscription/SubscriptionActivity.kt @@ -3,7 +3,6 @@ package com.chargebee.example.subscription import android.os.Bundle import android.util.Log import android.widget.Button -import com.chargebee.android.Chargebee import com.chargebee.example.BaseActivity import com.chargebee.example.R import com.chargebee.example.billing.BillingViewModel @@ -39,7 +38,7 @@ class SubscriptionActivity : BaseActivity() { Log.i(javaClass.simpleName, "Subscriptions by using queryParams: $it") if(it?.size!! >0) { val subscriptionStatus = - it?.get(0).cb_subscription.status + "\nPlan Price : " + it?.get(0)?.cb_subscription?.plan_amount; + (it?.get(0)?.cb_subscription?.status ?: "") + "\nPlan Price : " + it?.get(0)?.cb_subscription?.plan_amount; alertSuccess(subscriptionStatus) }else{ alertSuccess("Subscriptions not found in Chargebee System") diff --git a/app/src/main/java/com/chargebee/example/token/TokenViewModel.kt b/app/src/main/java/com/chargebee/example/token/TokenViewModel.kt index 3847ac1..525f918 100644 --- a/app/src/main/java/com/chargebee/example/token/TokenViewModel.kt +++ b/app/src/main/java/com/chargebee/example/token/TokenViewModel.kt @@ -4,7 +4,6 @@ import android.util.Log import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.chargebee.android.Chargebee -import com.chargebee.android.models.Token import com.chargebee.android.exceptions.InvalidRequestException import com.chargebee.android.exceptions.OperationFailedException import com.chargebee.android.exceptions.PaymentException diff --git a/app/src/main/java/com/chargebee/example/token/TokenizeActivity.kt b/app/src/main/java/com/chargebee/example/token/TokenizeActivity.kt index 0e6a4b1..033de23 100644 --- a/app/src/main/java/com/chargebee/example/token/TokenizeActivity.kt +++ b/app/src/main/java/com/chargebee/example/token/TokenizeActivity.kt @@ -5,7 +5,6 @@ import android.util.Log import android.widget.Button import android.widget.EditText import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import com.chargebee.android.models.Card import com.chargebee.android.models.PaymentDetail diff --git a/build.gradle b/build.gradle index d866a33..129bae1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.3.72" + ext.kotlin_version = "1.8.20" ext.assertj_version = '3.16.1' repositories { google() jcenter() } dependencies { - classpath "com.android.tools.build:gradle:4.0.0" + classpath 'com.android.tools.build:gradle:4.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/chargebee/build.gradle b/chargebee/build.gradle index d216def..2d46fd0 100644 --- a/chargebee/build.gradle +++ b/chargebee/build.gradle @@ -38,7 +38,7 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7" testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' // Google play billing library implementation 'com.android.billingclient:billing-ktx:5.2.1' @@ -54,7 +54,7 @@ dependencies { testImplementation 'org.json:json:20140107' - testImplementation 'androidx.test:core:1.2.0' + testImplementation 'androidx.test:core:1.4.0' testImplementation 'androidx.test.ext:junit:1.1.1' } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6c9a224..2e6e589 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 59ad17655d9bec7a3f7b58be622837cfac653d89 Mon Sep 17 00:00:00 2001 From: haripriyan Date: Thu, 23 Nov 2023 17:57:34 +0530 Subject: [PATCH 04/11] Refactors Product for BL5 --- app/build.gradle | 2 +- .../chargebee/example/ExampleApplication.kt | 2 +- .../example/adapter/ProductListAdapter.java | 26 ++- .../example/adapter/PurchaseProduct.java | 58 +++++ .../example/billing/BillingActivity.java | 50 +++-- .../example/billing/BillingViewModel.kt | 34 +-- .../billingservice/BillingClientManager.kt | 211 +++++++++--------- .../android/billingservice/CBPurchase.kt | 49 ++-- .../com/chargebee/android/models/Products.kt | 31 ++- .../android/models/PurchaseProductParams.kt | 6 + .../BillingClientManagerTest.kt | 4 +- 11 files changed, 268 insertions(+), 205 deletions(-) create mode 100644 app/src/main/java/com/chargebee/example/adapter/PurchaseProduct.java create mode 100644 chargebee/src/main/java/com/chargebee/android/models/PurchaseProductParams.kt diff --git a/app/build.gradle b/app/build.gradle index 887970e..0f20f06 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ android { } defaultConfig { applicationId "com.chargebee.example" - minSdkVersion 21 + minSdkVersion 24 targetSdkVersion 33 versionCode 6 versionName "1.0" diff --git a/app/src/main/java/com/chargebee/example/ExampleApplication.kt b/app/src/main/java/com/chargebee/example/ExampleApplication.kt index 5e7a473..02336f1 100644 --- a/app/src/main/java/com/chargebee/example/ExampleApplication.kt +++ b/app/src/main/java/com/chargebee/example/ExampleApplication.kt @@ -55,7 +55,7 @@ class ExampleApplication : Application(), NetworkUtil.NetworkListener { productIdList, object : CBCallback.ListProductsCallback> { override fun onSuccess(productIDs: ArrayList) { - if (productIDs.first().productType == ProductType.SUBS) + if (productIDs.first().type == ProductType.SUBS) validateReceipt(mContext, productIDs.first()) else validateNonSubscriptionReceipt(mContext, productIDs.first()) diff --git a/app/src/main/java/com/chargebee/example/adapter/ProductListAdapter.java b/app/src/main/java/com/chargebee/example/adapter/ProductListAdapter.java index 05d06f4..95da11e 100644 --- a/app/src/main/java/com/chargebee/example/adapter/ProductListAdapter.java +++ b/app/src/main/java/com/chargebee/example/adapter/ProductListAdapter.java @@ -7,19 +7,26 @@ import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; +import com.chargebee.android.billingservice.ProductType; import com.chargebee.android.models.CBProduct; +import com.chargebee.android.models.PricingPhase; +import com.chargebee.android.models.SubscriptionOffer; import com.chargebee.example.R; + +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; public class ProductListAdapter extends RecyclerView.Adapter { - private List mProductsList; + private List purchaseProducts; private ProductListAdapter.ProductClickListener mClickListener; private Context mContext = null; + private PurchaseProduct selectedProduct = null; - public ProductListAdapter(Context context, List mProductsList, ProductClickListener mClickListener) { + public ProductListAdapter(Context context, List purchaseProducts, ProductClickListener mClickListener) { mContext = context; - this.mProductsList = mProductsList; + this.purchaseProducts = purchaseProducts; this.mClickListener = mClickListener; } @@ -31,10 +38,12 @@ public ProductListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int vi @Override public void onBindViewHolder(ProductListAdapter.ViewHolder holder, int position) { - CBProduct products = mProductsList.get(position); - holder.mTextViewTitle.setText(products.getProductId() + products.getProductBasePlanId()); - holder.mTextViewPrice.setText(products.getProductPrice()); - if (products.getSubStatus()) { + PurchaseProduct purchaseProduct = purchaseProducts.get(position); + holder.mTextViewTitle.setText(purchaseProduct.getProductId() + " "+ purchaseProduct.getBasePlanId()); + holder.mTextViewPrice.setText(purchaseProduct.getPrice()); + boolean isSubscriptionProductSelected = selectedProduct != null && selectedProduct.getCbProduct().getType().equals(ProductType.SUBS) && selectedProduct.getOfferToken().equals(purchaseProduct.getOfferToken()); + boolean isOtpProductSelected = selectedProduct != null && selectedProduct.getCbProduct().getType().equals(ProductType.INAPP) && selectedProduct.getProductId().equals(purchaseProduct.getProductId()); + if (isSubscriptionProductSelected || isOtpProductSelected) { holder.mTextViewSubscribe.setText(R.string.status_subscribed); holder.mTextViewSubscribe.setTextColor(mContext.getResources().getColor(R.color.success_green)); }else { @@ -46,7 +55,7 @@ public void onBindViewHolder(ProductListAdapter.ViewHolder holder, int position) @Override public int getItemCount() { - return mProductsList.size(); + return purchaseProducts.size(); } public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { @@ -63,6 +72,7 @@ public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickL @Override public void onClick(View view) { if (mClickListener != null) { + selectedProduct = purchaseProducts.get(getAdapterPosition()); mClickListener.onProductClick(view, getAdapterPosition()); } } diff --git a/app/src/main/java/com/chargebee/example/adapter/PurchaseProduct.java b/app/src/main/java/com/chargebee/example/adapter/PurchaseProduct.java new file mode 100644 index 0000000..7d152b8 --- /dev/null +++ b/app/src/main/java/com/chargebee/example/adapter/PurchaseProduct.java @@ -0,0 +1,58 @@ +package com.chargebee.example.adapter; + +import com.chargebee.android.models.CBProduct; +import com.chargebee.android.models.PricingPhase; +import com.chargebee.android.models.SubscriptionOffer; + +public class PurchaseProduct { + private final String productId; + private final CBProduct cbProduct; + private final String basePlanId; + private final String offerId; + private final String offerToken; + private final String price; + + public PurchaseProduct(CBProduct cbProduct, SubscriptionOffer subscriptionOffer) { + this(cbProduct.getId(), cbProduct, subscriptionOffer.getBasePlanId(), subscriptionOffer.getOfferId(), + subscriptionOffer.getOfferToken(), subscriptionOffer.getPricingPhases().get(0).getFormattedPrice()); + } + + public PurchaseProduct(CBProduct cbProduct, PricingPhase oneTimePurchaseOffer) { + this(cbProduct.getId(), cbProduct, + null, null, + null, oneTimePurchaseOffer.getFormattedPrice()); + } + + public PurchaseProduct(String id, CBProduct cbProduct, String basePlanId, String offerId, String offerToken, String formattedPrice) { + this.productId = id; + this.cbProduct = cbProduct; + this.basePlanId = basePlanId; + this.offerId = offerId; + this.offerToken = offerToken; + this.price = formattedPrice; + } + + public String getProductId() { + return productId; + } + + public String getBasePlanId() { + return basePlanId; + } + + public String getOfferId() { + return offerId; + } + + public String getOfferToken() { + return offerToken; + } + + public String getPrice() { + return price; + } + + public CBProduct getCbProduct() { + return cbProduct; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/chargebee/example/billing/BillingActivity.java b/app/src/main/java/com/chargebee/example/billing/BillingActivity.java index fba9c6d..4c2c843 100644 --- a/app/src/main/java/com/chargebee/example/billing/BillingActivity.java +++ b/app/src/main/java/com/chargebee/example/billing/BillingActivity.java @@ -1,38 +1,40 @@ package com.chargebee.example.billing; +import static com.chargebee.example.util.Constants.PRODUCTS_LIST_KEY; + import android.app.Dialog; -import android.content.Context; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; -import androidx.annotation.NonNull; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + import com.chargebee.android.ProgressBarListener; -import com.chargebee.android.billingservice.CBCallback; -import com.chargebee.android.billingservice.CBPurchase; import com.chargebee.android.billingservice.OneTimeProductType; import com.chargebee.android.billingservice.ProductType; -import com.chargebee.android.exceptions.CBException; import com.chargebee.android.models.CBProduct; -import com.chargebee.android.models.NonSubscription; +import com.chargebee.android.models.PurchaseProductParams; import com.chargebee.android.network.CBCustomer; import com.chargebee.example.BaseActivity; import com.chargebee.example.R; import com.chargebee.example.adapter.ProductListAdapter; +import com.chargebee.example.adapter.PurchaseProduct; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; + import java.lang.reflect.Type; import java.util.ArrayList; -import static com.chargebee.example.util.Constants.PRODUCTS_LIST_KEY; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; public class BillingActivity extends BaseActivity implements ProductListAdapter.ProductClickListener, ProgressBarListener { - private ArrayList productList = null; + private List purchaseProducts = null; private ProductListAdapter productListAdapter = null; private LinearLayoutManager linearLayoutManager; private RecyclerView mItemsRecyclerView = null; @@ -55,10 +57,14 @@ protected void onCreate(Bundle savedInstanceState) { if(productDetails != null) { Gson gson = new Gson(); Type listType = new TypeToken>() {}.getType(); - productList = gson.fromJson(productDetails, listType); + List productList = gson.fromJson(productDetails, listType); + this.purchaseProducts = productList.stream() + .map(x -> toList(x)) + .flatMap(List::stream) + .collect(Collectors.toList()); } - productListAdapter = new ProductListAdapter(this,productList, this); + productListAdapter = new ProductListAdapter(this, purchaseProducts, this); linearLayoutManager = new LinearLayoutManager(this); mItemsRecyclerView.setLayoutManager(linearLayoutManager); mItemsRecyclerView.setItemAnimator(new DefaultItemAnimator()); @@ -158,7 +164,6 @@ private void getCustomerID() { } } else { purchaseProduct(customerId); - // purchaseProduct(); dialog.dismiss(); } }); @@ -174,26 +179,23 @@ private boolean checkProductTypeFiled(){ } private boolean isOneTimeProduct(){ - return productList.get(position).getProductType() == ProductType.INAPP; + return purchaseProducts.get(position).getCbProduct().getType() == ProductType.INAPP; } private void purchaseProduct(String customerId) { showProgressDialog(); - this.billingViewModel.purchaseProduct(this, productList.get(position), customerId); - } - - private void purchaseProduct() { - showProgressDialog(); - this.billingViewModel.purchaseProduct(this, productList.get(position), cbCustomer); + PurchaseProduct selectedPurchaseProduct = purchaseProducts.get(position); + PurchaseProductParams purchaseParams = new PurchaseProductParams(selectedPurchaseProduct.getCbProduct(), selectedPurchaseProduct.getOfferToken()); + this.billingViewModel.purchaseProduct(this, purchaseParams, cbCustomer); } private void purchaseNonSubscriptionProduct(OneTimeProductType productType) { showProgressDialog(); - this.billingViewModel.purchaseNonSubscriptionProduct(this, productList.get(position), cbCustomer, productType); + CBProduct selectedProduct = purchaseProducts.get(position).getCbProduct(); + this.billingViewModel.purchaseNonSubscriptionProduct(this, selectedProduct, cbCustomer, productType); } private void updateSubscribeStatus(){ - productList.get(position).setSubStatus(true); productListAdapter.notifyDataSetChanged(); } @@ -207,4 +209,12 @@ public void onShowProgressBar() { public void onHideProgressBar() { hideProgressDialog(); } + private List toList(CBProduct cbProduct) { + if(cbProduct.getType() == ProductType.SUBS) { + return cbProduct.getSubscriptionOffers().stream() + .map(x -> new PurchaseProduct(cbProduct, x)).collect(Collectors.toList()); + } else { + return Arrays.asList(new PurchaseProduct(cbProduct, cbProduct.getOneTimePurchaseOffer())); + } + } } diff --git a/app/src/main/java/com/chargebee/example/billing/BillingViewModel.kt b/app/src/main/java/com/chargebee/example/billing/BillingViewModel.kt index 1c66c00..8aa081a 100644 --- a/app/src/main/java/com/chargebee/example/billing/BillingViewModel.kt +++ b/app/src/main/java/com/chargebee/example/billing/BillingViewModel.kt @@ -28,10 +28,10 @@ class BillingViewModel : ViewModel() { private lateinit var sharedPreference : SharedPreferences var restorePurchaseResult: MutableLiveData> = MutableLiveData() - fun purchaseProduct(context: Context,product: CBProduct, customer: CBCustomer) { + fun purchaseProduct(context: Context, purchaseProductParams: PurchaseProductParams, customer: CBCustomer) { // Cache the product id in sharedPreferences and retry validating the receipt if in case server is not responding or no internet connection. sharedPreference = context.getSharedPreferences("PREFERENCE_NAME",Context.MODE_PRIVATE) - CBPurchase.purchaseProduct(product, customer, object : CBCallback.PurchaseCallback{ + CBPurchase.purchaseProduct(purchaseProductParams, customer, object : CBCallback.PurchaseCallback{ override fun onSuccess(result: ReceiptDetail, status:Boolean) { Log.i(TAG, "Subscription ID: ${result.subscription_id}") Log.i(TAG, "Plan ID: ${result.plan_id}") @@ -41,30 +41,8 @@ class BillingViewModel : ViewModel() { try { // Handled server not responding and offline if (error.httpStatusCode!! in 500..599) { - storeInLocal(product.productId) - validateReceipt(context = context, product = product) - } else { - cbException.postValue(error) - } - } catch (exp: Exception) { - Log.i(TAG, "Exception :${exp.message}") - } - } - }) - } - fun purchaseProduct(context: Context, product: CBProduct, customerId: String) { - sharedPreference = context.getSharedPreferences("PREFERENCE_NAME",Context.MODE_PRIVATE) - CBPurchase.purchaseProduct(product, customerId, object : CBCallback.PurchaseCallback{ - override fun onSuccess(result: ReceiptDetail, status:Boolean) { - Log.i(TAG, "Subscription ID: ${result.subscription_id}") - Log.i(TAG, "Plan ID: ${result.plan_id}") - productPurchaseResult.postValue(status) - } - override fun onError(error: CBException) { - try { - if (error.httpStatusCode!! in 500..599) { - storeInLocal(product.productId) - validateReceipt(context = context, product = product) + storeInLocal(purchaseProductParams.product.id) + validateReceipt(context = context, product = purchaseProductParams.product) } else { cbException.postValue(error) } @@ -107,7 +85,7 @@ class BillingViewModel : ViewModel() { } fun retrieveProductIdentifers(queryParam: Array){ - CBPurchase.retrieveProductIdentifers(queryParam) { + CBPurchase.retrieveProductIdentifiers(queryParam) { when (it) { is CBProductIDResult.ProductIds -> { Log.i(TAG, "List of Product Identifiers: $it") @@ -197,7 +175,7 @@ class BillingViewModel : ViewModel() { try { // Handled server not responding and offline if (error.httpStatusCode!! in 500..599) { - storeInLocal(product.productId) + storeInLocal(product.id) validateNonSubscriptionReceipt(context = context, product = product, productType = productType) } else { cbException.postValue(error) diff --git a/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt b/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt index f9c4107..b71a091 100644 --- a/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt +++ b/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt @@ -13,7 +13,10 @@ import com.chargebee.android.exceptions.CBException import com.chargebee.android.exceptions.ChargebeeResult import com.chargebee.android.models.CBNonSubscriptionResponse import com.chargebee.android.models.CBProduct +import com.chargebee.android.models.PricingPhase +import com.chargebee.android.models.PurchaseProductParams import com.chargebee.android.models.PurchaseTransaction +import com.chargebee.android.models.SubscriptionOffer import com.chargebee.android.network.CBReceiptResponse import com.chargebee.android.restore.CBRestorePurchaseManager @@ -25,7 +28,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { private val handler = Handler(Looper.getMainLooper()) private var purchaseCallBack: CBCallback.PurchaseCallback? = null private val TAG = javaClass.simpleName - lateinit var product: CBProduct + private lateinit var purchaseProductParams: PurchaseProductParams private lateinit var restorePurchaseCallBack: CBCallback.RestorePurchaseCallback private var oneTimePurchaseCallback: CBCallback.OneTimePurchaseCallback? = null @@ -96,18 +99,8 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { try { val cbProductDetails = arrayListOf() for (productDetail in productsDetail) { - - val subscriptionOfferDetails = productDetail.subscriptionOfferDetails - subscriptionOfferDetails?.forEach { - val product = subscriptionCbProduct(productDetail, it) - cbProductDetails.add(product) - } - - val oneTimePurchaseOfferDetails = productDetail.oneTimePurchaseOfferDetails - oneTimePurchaseOfferDetails?.let { - val product = oneTimeCbProduct(productDetail, it) - cbProductDetails.add(product) - } + val cbProduct = convertToCbProduct(productDetail) + cbProductDetails.add(cbProduct) } Log.i(TAG, "Product details :$cbProductDetails") response(cbProductDetails) @@ -134,58 +127,61 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { errorDetail(CBException(ErrorDetail(message = "${exp.message}"))) } } - - private fun oneTimeCbProduct( - productDetail: ProductDetails, - it: ProductDetails.OneTimePurchaseOfferDetails - ): CBProduct { - val productId = productDetail.productId - val productType = ProductType.getProductType(productDetail.productType) - val productTitle = productDetail.title - val price = it.formattedPrice + private fun convertToCbProduct(productDetail: ProductDetails): CBProduct { + val subscriptionOffers = subscriptionOffers(productDetail.subscriptionOfferDetails) + val oneTimePurchaseOffer = oneTimePurchaseOffer(productDetail.oneTimePurchaseOfferDetails) return CBProduct( - productId, - productTitle, - null, - price, - productDetail, - null, - false, - productType + productDetail.productId, + productDetail.title, + productDetail.description, + ProductType.getProductType(productDetail.productType), + subscriptionOffers, + oneTimePurchaseOffer ) } - private fun subscriptionCbProduct( - productDetail: ProductDetails, - it: ProductDetails.SubscriptionOfferDetails - ): CBProduct { - val productId = productDetail.productId - val productType = ProductType.getProductType(productDetail.productType) - val productTitle = productDetail.title - val price = it.pricingPhases?.pricingPhaseList?.first()?.formattedPrice ?: "0" - val offerToken = it.offerToken - val basePlanId = it.basePlanId + private fun oneTimePurchaseOffer(oneTimePurchaseOfferDetails: ProductDetails.OneTimePurchaseOfferDetails?): PricingPhase? { + return oneTimePurchaseOfferDetails?.let { + return PricingPhase(oneTimePurchaseOfferDetails.formattedPrice, + oneTimePurchaseOfferDetails.priceAmountMicros, + oneTimePurchaseOfferDetails.priceCurrencyCode) + } + } + + private fun subscriptionOffers(subscriptionOfferDetails: List?): List? { + return subscriptionOfferDetails?.let { it.map { i -> subscriptionOffer(i) } } + } - return CBProduct( - productId, - productTitle, - basePlanId, - price, - productDetail, - offerToken, - false, - productType + private fun subscriptionOffer(subscriptionOfferDetail: ProductDetails.SubscriptionOfferDetails): SubscriptionOffer { + val pricingPhases = pricingPhases(subscriptionOfferDetail.pricingPhases) + return SubscriptionOffer( + subscriptionOfferDetail.basePlanId, + subscriptionOfferDetail.offerId, + subscriptionOfferDetail.offerToken, + pricingPhases ) } + private fun pricingPhases(pricingPhases: ProductDetails.PricingPhases): List { + return pricingPhases.pricingPhaseList.map { + PricingPhase( + it.formattedPrice, + it.priceAmountMicros, + it.priceCurrencyCode, + it.billingPeriod, + it.billingCycleCount + ) + } + } + internal fun purchase( - product: CBProduct, + purchaseProductParams: PurchaseProductParams, purchaseCallBack: CBCallback.PurchaseCallback ) { this.purchaseCallBack = purchaseCallBack onConnected({ status -> if (status) { - purchase(product) + purchase(purchaseProductParams) } else purchaseCallBack.onError( connectionError @@ -197,47 +193,58 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { } /* Purchase the product: Initiates the billing flow for an In-app-purchase */ - private fun purchase(product: CBProduct) { - this.product = product - val productDetails = product.productDetails - val offerToken = product.offerToken - - val productDetailsParamsList = - listOf( - offerToken?.let { - BillingFlowParams.ProductDetailsParams.newBuilder() - .setProductDetails(productDetails) - .setOfferToken(it) + private fun purchase(purchaseProductParams: PurchaseProductParams) { + this.purchaseProductParams = purchaseProductParams + val offerToken = purchaseProductParams.offerToken + + val queryProductDetails = arrayListOf(QueryProductDetailsParams.Product.newBuilder() + .setProductId(purchaseProductParams.product.id) + .setProductType(purchaseProductParams.product.type.value) + .build()) + + val productDetailsParams = QueryProductDetailsParams.newBuilder().setProductList(queryProductDetails).build() + + billingClient?.queryProductDetailsAsync( + productDetailsParams + ) { billingResult, productsDetail -> + if (billingResult.responseCode == OK && productsDetail != null) { + val productDetailsBuilder = BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(productsDetail.first()) + offerToken?.let { productDetailsBuilder.setOfferToken(it) } + val productDetailsParamsList = + listOf(productDetailsBuilder.build()) + + val billingFlowParams = + BillingFlowParams.newBuilder() + .setProductDetailsParamsList(productDetailsParamsList) .build() - } - ) - val billingFlowParams = - BillingFlowParams.newBuilder() - .setProductDetailsParamsList(productDetailsParamsList) - .build() - - billingClient?.launchBillingFlow(mContext as Activity, billingFlowParams) - .takeIf { billingResult -> - billingResult?.responseCode != OK - }?.let { billingResult -> - Log.e(TAG, "Failed to launch billing flow $billingResult") - val billingError = CBException( - ErrorDetail( - message = GPErrorCode.LaunchBillingFlowError.errorMsg, - httpStatusCode = billingResult.responseCode - ) - ) - if (ProductType.SUBS == product.productType) { - purchaseCallBack?.onError( - billingError - ) - } else { - oneTimePurchaseCallback?.onError( - billingError - ) - } + billingClient?.launchBillingFlow(mContext as Activity, billingFlowParams) + .takeIf { billingResult -> + billingResult?.responseCode != OK + }?.let { billingResult -> + Log.e(TAG, "Failed to launch billing flow $billingResult") + val billingError = CBException( + ErrorDetail( + message = GPErrorCode.LaunchBillingFlowError.errorMsg, + httpStatusCode = billingResult.responseCode + ) + ) + if (ProductType.SUBS == purchaseProductParams.product.type) { + purchaseCallBack?.onError( + billingError + ) + } else { + oneTimePurchaseCallback?.onError( + billingError + ) + } + } + } else { + // TODO: Handle error } + } + } /** @@ -319,7 +326,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { } else -> { - if (product.productType == ProductType.SUBS) + if (purchaseProductParams.product.type == ProductType.SUBS) purchaseCallBack?.onError( throwCBException(billingResult) ) @@ -333,10 +340,10 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { /* Acknowledge the Purchases */ private fun acknowledgePurchase(purchase: Purchase) { - when (product.productType) { + when (purchaseProductParams.product.type) { ProductType.SUBS -> { isAcknowledgedPurchase(purchase, { - validateReceipt(purchase.purchaseToken, product) + validateReceipt(purchase.purchaseToken, purchaseProductParams.product) }, { purchaseCallBack?.onError(it) }) @@ -347,7 +354,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { consumeAsyncPurchase(purchase.purchaseToken) } else { isAcknowledgedPurchase(purchase, { - validateNonSubscriptionReceipt(purchase.purchaseToken, product) + validateNonSubscriptionReceipt(purchase.purchaseToken, purchaseProductParams.product) }, { oneTimePurchaseCallback?.onError(it) }) @@ -399,7 +406,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { consumePurchase(token) { billingResult, purchaseToken -> when (billingResult.responseCode) { OK -> { - validateNonSubscriptionReceipt(purchaseToken, product) + validateNonSubscriptionReceipt(purchaseToken, purchaseProductParams.product) } else -> { @@ -609,7 +616,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { if (status) queryPurchaseHistory { purchaseHistoryList -> val purchaseTransaction = purchaseHistoryList.filter { - it.productId.first() == product.productId + it.productId.first() == product.id } val transaction = purchaseTransaction.firstOrNull() transaction?.let { @@ -633,12 +640,12 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { ) { this.oneTimePurchaseCallback = oneTimePurchaseCallback onConnected({ status -> - if (status) - purchase(product) - else - oneTimePurchaseCallback.onError( - connectionError - ) + if (status) { + val purchaseParams = PurchaseProductParams(product) + purchase(purchaseParams) + } else { + oneTimePurchaseCallback.onError(connectionError) + } }, { error -> oneTimePurchaseCallback.onError(error) }) @@ -687,7 +694,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { if (status) queryPurchaseHistory { purchaseHistoryList -> val purchaseTransaction = purchaseHistoryList.filter { - it.productId.first() == product.productId + it.productId.first() == product.id } val transaction = purchaseTransaction.firstOrNull() transaction?.let { diff --git a/chargebee/src/main/java/com/chargebee/android/billingservice/CBPurchase.kt b/chargebee/src/main/java/com/chargebee/android/billingservice/CBPurchase.kt index 34e871f..e5c41f6 100644 --- a/chargebee/src/main/java/com/chargebee/android/billingservice/CBPurchase.kt +++ b/chargebee/src/main/java/com/chargebee/android/billingservice/CBPurchase.kt @@ -25,7 +25,7 @@ object CBPurchase { * Get the product ID's from chargebee system */ @JvmStatic - fun retrieveProductIdentifers( + fun retrieveProductIdentifiers( params: Array = arrayOf(), completion: (CBProductIDResult>) -> Unit ) { @@ -54,41 +54,24 @@ object CBPurchase { } /** - * Buy the product with/without customer id - * @param [product] The product that wish to purchase - * @param [callback] listener will be called when product purchase completes. - */ - @Deprecated( - message = "This will be removed in upcoming release, Please use API fun - purchaseProduct(product: CBProduct, customer : CBCustomer? = null, callback)", - level = DeprecationLevel.WARNING - ) - @JvmStatic - fun purchaseProduct( - product: CBProduct, customerID: String, - callback: CBCallback.PurchaseCallback - ) { - customer = CBCustomer(customerID, "", "", "") - purchaseProduct(product, callback) - } - - /** - * Buy the product with/without customer data - * @param [product] The product that wish to purchase + * Buy Subscription product with/without customer data + * @param [purchaseParams] The purchase parameters of the product to be purchased. + * @param [customer] Optional. Customer Object. * @param [callback] listener will be called when product purchase completes. */ @JvmStatic fun purchaseProduct( - product: CBProduct, customer: CBCustomer? = null, + purchaseProductParams: PurchaseProductParams, customer: CBCustomer? = null, callback: CBCallback.PurchaseCallback ) { this.customer = customer - purchaseProduct(product, callback) + purchaseProduct(purchaseProductParams, callback) } - private fun purchaseProduct(product: CBProduct, callback: CBCallback.PurchaseCallback) { + private fun purchaseProduct(purchaseProductParams: PurchaseProductParams, callback: CBCallback.PurchaseCallback) { isSDKKeyValid({ - log(customer, product) - billingClientManager?.purchase(product, callback) + log(customer, purchaseProductParams.product.id) + billingClientManager?.purchase(purchaseProductParams, callback) }, { callback.onError(it) }) @@ -111,7 +94,7 @@ object CBPurchase { this.productType = productType isSDKKeyValid({ - log(CBPurchase.customer, product, productType) + log(CBPurchase.customer, product.id, productType) billingClientManager?.purchaseNonSubscriptionProduct(product, callback) }, { callback.onError(it) @@ -195,7 +178,7 @@ object CBPurchase { completion: (ChargebeeResult) -> Unit ) { try { - validateReceipt(purchaseToken, product.productId, completion) + validateReceipt(purchaseToken, product.id, completion) } catch (exp: Exception) { Log.e(javaClass.simpleName, "Exception in validateReceipt() :" + exp.message) ChargebeeResult.Error( @@ -257,7 +240,7 @@ object CBPurchase { product: CBProduct, completion: (ChargebeeResult) -> Unit ) { - validateNonSubscriptionReceipt(purchaseToken, product.productId, completion) + validateNonSubscriptionReceipt(purchaseToken, product.id, completion) } internal fun validateNonSubscriptionReceipt( @@ -384,8 +367,8 @@ object CBPurchase { return billingClientManager as BillingClientManager } - private fun log(customer: CBCustomer?, product: CBProduct, productType: OneTimeProductType? = null) { - val additionalInfo = additionalInfo(customer, product, productType) + private fun log(customer: CBCustomer?, productId: String, productType: OneTimeProductType? = null) { + val additionalInfo = additionalInfo(customer, productId, productType) val logger = CBLogger( name = "buy", action = "before_purchase_command", @@ -393,8 +376,8 @@ object CBPurchase { ) ResultHandler.safeExecute { logger.info() } } - private fun additionalInfo(customer: CBCustomer?, product: CBProduct, productType: OneTimeProductType? = null): Map { - val map = mutableMapOf("product" to product.productId) + private fun additionalInfo(customer: CBCustomer?, productId: String, productType: OneTimeProductType? = null): Map { + val map = mutableMapOf("product" to productId) customer?.let { map["customerId"] = (it.id ?: "") } productType?.let { map["productType"] = it.toString() } return map diff --git a/chargebee/src/main/java/com/chargebee/android/models/Products.kt b/chargebee/src/main/java/com/chargebee/android/models/Products.kt index ad9c608..a915bf6 100644 --- a/chargebee/src/main/java/com/chargebee/android/models/Products.kt +++ b/chargebee/src/main/java/com/chargebee/android/models/Products.kt @@ -1,15 +1,26 @@ package com.chargebee.android.models -import com.android.billingclient.api.ProductDetails import com.chargebee.android.billingservice.ProductType data class CBProduct( - val productId: String, - val productTitle: String, - val productBasePlanId: String?, - val productPrice: String, - var productDetails: ProductDetails, - var offerToken: String?, - var subStatus: Boolean, - var productType: ProductType -) \ No newline at end of file + val id: String, + val title: String, + val description: String, + val type: ProductType, + val subscriptionOffers: List?, + val oneTimePurchaseOffer: PricingPhase?, +) + +data class SubscriptionOffer( + val basePlanId: String, + val offerId: String?, + val offerToken: String, + val pricingPhases: List +) +data class PricingPhase( + val formattedPrice: String, + val amountInMicros: Long, + val currencyCode: String, + val billingPeriod: String? = null, + val billingCycleCount: Int? = null +) diff --git a/chargebee/src/main/java/com/chargebee/android/models/PurchaseProductParams.kt b/chargebee/src/main/java/com/chargebee/android/models/PurchaseProductParams.kt new file mode 100644 index 0000000..1d4dd71 --- /dev/null +++ b/chargebee/src/main/java/com/chargebee/android/models/PurchaseProductParams.kt @@ -0,0 +1,6 @@ +package com.chargebee.android.models + +data class PurchaseProductParams( + val product: CBProduct, + val offerToken: String? = null +) \ No newline at end of file diff --git a/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt b/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt index 5befe09..4da7f86 100644 --- a/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt +++ b/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt @@ -151,7 +151,7 @@ class BillingClientManagerTest { val IDs = java.util.ArrayList() IDs.add("") CoroutineScope(Dispatchers.IO).launch { - Mockito.`when`(CBPurchase.retrieveProductIdentifers(queryParam) { + Mockito.`when`(CBPurchase.retrieveProductIdentifiers(queryParam) { when (it) { is CBProductIDResult.ProductIds -> { assertThat(it,instanceOf(CBProductIDResult::class.java)) @@ -235,7 +235,7 @@ class BillingClientManagerTest { val productsIds = java.util.ArrayList() productsIds.add("") CoroutineScope(Dispatchers.IO).launch { - Mockito.`when`(CBPurchase.retrieveProductIdentifers(queryParam) { + Mockito.`when`(CBPurchase.retrieveProductIdentifiers(queryParam) { when (it) { is CBProductIDResult.ProductIds -> { assertThat(it,instanceOf(CBProductIDResult::class.java)) From 09c6ddfba9a72930c2de40e84b3b72f3c2e71505 Mon Sep 17 00:00:00 2001 From: haripriyan Date: Thu, 23 Nov 2023 23:58:52 +0530 Subject: [PATCH 05/11] Refactors example app --- .../chargebee/example/ExampleApplication.kt | 13 ++++--- .../com/chargebee/example/MainActivity.kt | 15 ++++++-- .../java/com/chargebee/android/Chargebee.kt | 35 ------------------- 3 files changed, 19 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/chargebee/example/ExampleApplication.kt b/app/src/main/java/com/chargebee/example/ExampleApplication.kt index 02336f1..0dae9b5 100644 --- a/app/src/main/java/com/chargebee/example/ExampleApplication.kt +++ b/app/src/main/java/com/chargebee/example/ExampleApplication.kt @@ -18,7 +18,7 @@ import com.chargebee.example.util.NetworkUtil class ExampleApplication : Application(), NetworkUtil.NetworkListener { private lateinit var networkUtil: NetworkUtil - private lateinit var sharedPreference: SharedPreferences + private var sharedPreference: SharedPreferences? = null lateinit var mContext: Context private val customer = CBCustomer( id = "sync_receipt_android", @@ -36,8 +36,7 @@ class ExampleApplication : Application(), NetworkUtil.NetworkListener { } override fun onNetworkConnectionAvailable() { - Chargebee.configure(site = "", publishableApiKey= "",sdkKey= "", packageName = this.packageName) - val productId = sharedPreference.getString("productId", "") + val productId = sharedPreference?.getString("productId", "") if (productId?.isNotEmpty() == true) { val productList = ArrayList() productList.add(productId) @@ -76,8 +75,8 @@ class ExampleApplication : Application(), NetworkUtil.NetworkListener { completionCallback = object : CBCallback.PurchaseCallback { override fun onSuccess(result: ReceiptDetail, status: Boolean) { // Clear the local cache once receipt validation success - val editor = sharedPreference.edit() - editor.clear().apply() + val editor = sharedPreference?.edit() + editor?.clear()?.apply() Log.i(javaClass.simpleName, "Subscription ID: ${result.subscription_id}") Log.i(javaClass.simpleName, "Plan ID: ${result.plan_id}") Log.i(javaClass.simpleName, "Customer ID: ${result.customer_id}") @@ -99,8 +98,8 @@ class ExampleApplication : Application(), NetworkUtil.NetworkListener { completionCallback = object : CBCallback.OneTimePurchaseCallback { override fun onSuccess(result: NonSubscription, status: Boolean) { // Clear the local cache once receipt validation success - val editor = sharedPreference.edit() - editor.clear().apply() + val editor = sharedPreference?.edit() + editor?.clear()?.apply() Log.i(javaClass.simpleName, "Subscription ID: ${result.invoiceId}") Log.i(javaClass.simpleName, "Plan ID: ${result.chargeId}") Log.i(javaClass.simpleName, "Customer ID: ${result.customerId}") diff --git a/app/src/main/java/com/chargebee/example/MainActivity.kt b/app/src/main/java/com/chargebee/example/MainActivity.kt index 5520c6c..98750f4 100644 --- a/app/src/main/java/com/chargebee/example/MainActivity.kt +++ b/app/src/main/java/com/chargebee/example/MainActivity.kt @@ -17,6 +17,7 @@ import com.chargebee.android.Chargebee import com.chargebee.android.billingservice.CBCallback import com.chargebee.android.billingservice.CBPurchase import com.chargebee.android.exceptions.CBException +import com.chargebee.android.exceptions.ChargebeeResult import com.chargebee.android.models.CBProduct import com.chargebee.example.adapter.ListItemsAdapter import com.chargebee.example.addon.AddonActivity @@ -175,8 +176,18 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener { siteNameEditText.text.toString(), apiKeyEditText.text.toString(), true, - sdkKeyEditText.text.toString(), this.packageName - ) + sdkKeyEditText.text.toString(), + this.packageName + ) { + when (it) { + is ChargebeeResult.Success -> { + Log.i(javaClass.simpleName, "Configured") + } + is ChargebeeResult.Error -> { + Log.e(javaClass.simpleName, " Failed") + } + } + } } builder.show() } diff --git a/chargebee/src/main/java/com/chargebee/android/Chargebee.kt b/chargebee/src/main/java/com/chargebee/android/Chargebee.kt index 785de24..d75c6e4 100644 --- a/chargebee/src/main/java/com/chargebee/android/Chargebee.kt +++ b/chargebee/src/main/java/com/chargebee/android/Chargebee.kt @@ -42,41 +42,6 @@ object Chargebee { private const val SUBSCRIPTION_URL = "https://play.google.com/store/account/subscriptions?sku=%s&package=%s" - /* Configure the app details with chargebee system */ - fun configure( - site: String, - publishableApiKey: String, - allowErrorLogging: Boolean = true, - sdkKey: String = "", - packageName: String = "" - ) { - this.applicationId = packageName - this.publishableApiKey = publishableApiKey - this.site = site - this.encodedApiKey = Credentials.basic(publishableApiKey, "") - this.baseUrl = "https://${site}.chargebee.com/api/" - this.allowErrorLogging = allowErrorLogging - this.sdkKey = sdkKey - val auth = Auth(sdkKey, applicationId, appName, channel) - - CBAuthentication.authenticate(auth) { - when (it) { - is ChargebeeResult.Success -> { - Log.i(javaClass.simpleName, "Environment Setup Completed") - Log.i(javaClass.simpleName, " Response :${it.data}") - val response = it.data as CBAuthResponse - this.version = response.in_app_detail.product_catalog_version - this.applicationId = response.in_app_detail.app_id - this.appName = response.in_app_detail.app_name - } - is ChargebeeResult.Error -> { - Log.i(javaClass.simpleName, "Exception from server :${it.exp.message}") - this.version = CatalogVersion.Unknown.value - } - } - } - } - /* Configure the app details with chargebee system */ fun configure( site: String, From 4da4427d1a8c28c8b8cd12ace58936e34ac8e6d5 Mon Sep 17 00:00:00 2001 From: haripriyan Date: Fri, 24 Nov 2023 00:00:49 +0530 Subject: [PATCH 06/11] Updates version --- chargebee/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chargebee/build.gradle b/chargebee/build.gradle index 2d46fd0..ffe3cde 100644 --- a/chargebee/build.gradle +++ b/chargebee/build.gradle @@ -8,7 +8,7 @@ android { minSdkVersion 21 targetSdkVersion 31 versionCode 1 - versionName "2.0.0" + versionName "2.0.0-beta-1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" From 0d6fe7daf181f68d2200e9fc61b11c79699e41c3 Mon Sep 17 00:00:00 2001 From: haripriyan Date: Fri, 24 Nov 2023 16:32:45 +0530 Subject: [PATCH 07/11] Fixes unit tests --- app/src/main/res/layout/activity_main.xml | 2 +- .../java/com/chargebee/android/Chargebee.kt | 35 +++++++++++++++++++ .../billingservice/BillingClientManager.kt | 8 ++++- .../BillingClientManagerTest.kt | 33 ++++++++++------- .../android/fixtures/ProductFixtures.kt | 15 ++++++++ .../android/resources/ItemResourceTest.kt | 4 ++- 6 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 chargebee/src/test/java/com/chargebee/android/fixtures/ProductFixtures.kt diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 43cdfa6..931cca5 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -22,7 +22,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="10dp" - android:text="Chargebee Examples" + android:text="Chargebee Examples - V1.0" android:textSize="24sp" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" diff --git a/chargebee/src/main/java/com/chargebee/android/Chargebee.kt b/chargebee/src/main/java/com/chargebee/android/Chargebee.kt index d75c6e4..785de24 100644 --- a/chargebee/src/main/java/com/chargebee/android/Chargebee.kt +++ b/chargebee/src/main/java/com/chargebee/android/Chargebee.kt @@ -42,6 +42,41 @@ object Chargebee { private const val SUBSCRIPTION_URL = "https://play.google.com/store/account/subscriptions?sku=%s&package=%s" + /* Configure the app details with chargebee system */ + fun configure( + site: String, + publishableApiKey: String, + allowErrorLogging: Boolean = true, + sdkKey: String = "", + packageName: String = "" + ) { + this.applicationId = packageName + this.publishableApiKey = publishableApiKey + this.site = site + this.encodedApiKey = Credentials.basic(publishableApiKey, "") + this.baseUrl = "https://${site}.chargebee.com/api/" + this.allowErrorLogging = allowErrorLogging + this.sdkKey = sdkKey + val auth = Auth(sdkKey, applicationId, appName, channel) + + CBAuthentication.authenticate(auth) { + when (it) { + is ChargebeeResult.Success -> { + Log.i(javaClass.simpleName, "Environment Setup Completed") + Log.i(javaClass.simpleName, " Response :${it.data}") + val response = it.data as CBAuthResponse + this.version = response.in_app_detail.product_catalog_version + this.applicationId = response.in_app_detail.app_id + this.appName = response.in_app_detail.app_name + } + is ChargebeeResult.Error -> { + Log.i(javaClass.simpleName, "Exception from server :${it.exp.message}") + this.version = CatalogVersion.Unknown.value + } + } + } + } + /* Configure the app details with chargebee system */ fun configure( site: String, diff --git a/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt b/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt index b71a091..a59f2dd 100644 --- a/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt +++ b/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt @@ -241,7 +241,13 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { } } } else { - // TODO: Handle error + Log.e(TAG, "Failed to fetch product :" + billingResult.responseCode) + if (ProductType.SUBS == purchaseProductParams.product.type) { + purchaseCallBack?.onError(throwCBException(billingResult)) + } else { + oneTimePurchaseCallback?.onError(throwCBException(billingResult)) + } + } } diff --git a/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt b/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt index 4da7f86..700f81b 100644 --- a/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt +++ b/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt @@ -10,9 +10,12 @@ import com.chargebee.android.billingservice.CBCallback.ListProductsCallback import com.chargebee.android.exceptions.CBException import com.chargebee.android.exceptions.CBProductIDResult import com.chargebee.android.exceptions.ChargebeeResult +import com.chargebee.android.fixtures.otpProducts +import com.chargebee.android.fixtures.subProducts import com.chargebee.android.models.CBNonSubscriptionResponse import com.chargebee.android.models.CBProduct import com.chargebee.android.models.NonSubscription +import com.chargebee.android.models.PurchaseProductParams import com.chargebee.android.network.* import com.chargebee.android.resources.CatalogVersion import com.chargebee.android.resources.ReceiptResource @@ -62,8 +65,7 @@ class BillingClientManagerTest { private var callBackOneTimePurchase: CBCallback.OneTimePurchaseCallback? = null private val nonSubscriptionDetail = NonSubscription("invoiceId", "customerId", "chargeId") private val productDetails = ProductDetails::class.create() - private val otpProducts = CBProduct("test.consumable","Example product","basePlanId", "100.0", productDetails,"offerToken", true, productType = ProductType.INAPP) - private val subProducts = CBProduct("chargebee.premium.android","Premium Plan","basePlanId", "", productDetails,"offerToken", true, ProductType.SUBS) + @Before fun setUp() { @@ -256,8 +258,9 @@ class BillingClientManagerTest { val lock = CountDownLatch(1) CoroutineScope(Dispatchers.IO).launch { + val purchaseProductParams = PurchaseProductParams(subProducts) CBPurchase.purchaseProduct( - subProducts,"", + purchaseProductParams,null, object : CBCallback.PurchaseCallback { override fun onError(error: CBException) { lock.countDown() @@ -271,10 +274,10 @@ class BillingClientManagerTest { }) Mockito.`when`(callBackPurchase?.let { - billingClientManager?.purchase(subProducts, it) + billingClientManager?.purchase(purchaseProductParams, it) }).thenReturn(Unit) callBackPurchase?.let { - verify(billingClientManager, times(1))?.purchase(subProducts,purchaseCallBack = it) + verify(billingClientManager, times(1))?.purchase(purchaseProductParams,purchaseCallBack = it) } } lock.await() @@ -285,8 +288,9 @@ class BillingClientManagerTest { val skuDetails = SkuDetails(jsonDetails) CoroutineScope(Dispatchers.IO).launch { + val purchaseProductParams = PurchaseProductParams(subProducts) CBPurchase.purchaseProduct( - subProducts,"", + purchaseProductParams,null, object : CBCallback.PurchaseCallback { override fun onSuccess(result: ReceiptDetail, status: Boolean) { @@ -364,8 +368,9 @@ class BillingClientManagerTest { val skuDetails = SkuDetails(jsonDetails) val lock = CountDownLatch(1) CoroutineScope(Dispatchers.IO).launch { + val purchaseProductParams = PurchaseProductParams(subProducts) CBPurchase.purchaseProduct( - subProducts,customer, + purchaseProductParams,customer, object : CBCallback.PurchaseCallback { override fun onError(error: CBException) { lock.countDown() @@ -379,10 +384,10 @@ class BillingClientManagerTest { }) Mockito.`when`(callBackPurchase?.let { - billingClientManager?.purchase(subProducts, it) + billingClientManager?.purchase(purchaseProductParams, it) }).thenReturn(Unit) callBackPurchase?.let { - verify(billingClientManager, times(1))?.purchase(subProducts,purchaseCallBack = it) + verify(billingClientManager, times(1))?.purchase(purchaseProductParams,purchaseCallBack = it) } } lock.await() @@ -395,8 +400,9 @@ class BillingClientManagerTest { val skuDetails = SkuDetails(jsonDetails) val lock = CountDownLatch(1) CoroutineScope(Dispatchers.IO).launch { + val purchaseProductParams = PurchaseProductParams(subProducts) CBPurchase.purchaseProduct( - subProducts,customer, + purchaseProductParams,customer, object : CBCallback.PurchaseCallback { override fun onError(error: CBException) { lock.countDown() @@ -410,10 +416,10 @@ class BillingClientManagerTest { }) Mockito.`when`(callBackPurchase?.let { - billingClientManager?.purchase(subProducts, it) + billingClientManager?.purchase(purchaseProductParams, it) }).thenReturn(Unit) callBackPurchase?.let { - verify(billingClientManager, times(1))?.purchase(subProducts,purchaseCallBack = it) + verify(billingClientManager, times(1))?.purchase(purchaseProductParams,purchaseCallBack = it) } } lock.await() @@ -424,8 +430,9 @@ class BillingClientManagerTest { val skuDetails = SkuDetails(jsonDetails) CoroutineScope(Dispatchers.IO).launch { + val purchaseProductParams = PurchaseProductParams(subProducts) CBPurchase.purchaseProduct( - subProducts,customer, + purchaseProductParams,customer, object : CBCallback.PurchaseCallback { override fun onSuccess(result: ReceiptDetail, status: Boolean) { diff --git a/chargebee/src/test/java/com/chargebee/android/fixtures/ProductFixtures.kt b/chargebee/src/test/java/com/chargebee/android/fixtures/ProductFixtures.kt new file mode 100644 index 0000000..f25002d --- /dev/null +++ b/chargebee/src/test/java/com/chargebee/android/fixtures/ProductFixtures.kt @@ -0,0 +1,15 @@ +package com.chargebee.android.fixtures + +import com.chargebee.android.billingservice.ProductType +import com.chargebee.android.models.CBProduct +import com.chargebee.android.models.PricingPhase +import com.chargebee.android.models.SubscriptionOffer + +val subsPricingPhase: PricingPhase = PricingPhase(formattedPrice = "1100.0 INR", amountInMicros = 1100000, currencyCode = "INR") +val subscriptionOffers: List = arrayListOf(SubscriptionOffer("basePlanId", "offerId", "offerToken", arrayListOf(subsPricingPhase))) +val oneTimePurchaseOffer: PricingPhase = PricingPhase(formattedPrice = "100.0 INR", amountInMicros = 100000, currencyCode = "INR") +val otpProducts = CBProduct("test.consumable","Example product", + "Description",ProductType.INAPP,null, oneTimePurchaseOffer) +val subProducts = CBProduct("chargebee.premium.android","Premium Plan", + "Description",ProductType.SUBS, subscriptionOffers, null) + diff --git a/chargebee/src/test/java/com/chargebee/android/resources/ItemResourceTest.kt b/chargebee/src/test/java/com/chargebee/android/resources/ItemResourceTest.kt index fff77f4..a987277 100644 --- a/chargebee/src/test/java/com/chargebee/android/resources/ItemResourceTest.kt +++ b/chargebee/src/test/java/com/chargebee/android/resources/ItemResourceTest.kt @@ -32,7 +32,9 @@ class ItemResourceTest { site = "cb-imay-test", publishableApiKey = "test_EojsGoGFeHoc3VpGPQDOZGAxYy3d0FF3", sdkKey = "cb-j53yhbfmtfhfhkmhow3ramecom" - ) + ) { + + } } @After From 41025c14e8bf85c0b7db518388a6fb484079d779 Mon Sep 17 00:00:00 2001 From: haripriyan Date: Fri, 24 Nov 2023 22:45:52 +0530 Subject: [PATCH 08/11] Updates docs --- README.md | 46 +++++++++++++------ .../com/chargebee/example/MainActivity.kt | 9 ++-- .../example/billing/BillingViewModel.kt | 2 +- .../android/billingservice/CBPurchase.kt | 2 +- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 8a7905e..706cc2d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ # Chargebee Android + +> #### Updates for Billing Library 5 +> ***Note**: If you want to simply update your app to use Google Billing Library 5, without having to make related changes, you can use the [1.x version](https://github.com/chargebee/chargebee-android/tree/1.x.x). You can upgrade to the latest version once you are ready. +> +> If you are getting started with the integration or ready to leverage Google Billing Library 5 changes, follow this [link](https://www.chargebee.com/docs/2.0/mobile-playstore-billing-library-5.html) for getting started. + This is Chargebee’s Android Software Development Kit (SDK). This SDK makes it efficient and comfortable to build a seamless subscription experience in your Android app. Post-installation, initialization, and authentication with the Chargebee site, this SDK will support the following process. @@ -21,7 +27,7 @@ The following requirements must be set up before installing Chargebee’s Androi The `Chargebee-Android` SDK can be installed by adding below dependency to the `build.gradle` file: ```kotlin -implementation 'com.chargebee:chargebee-android:1.2.0' +implementation 'com.chargebee:chargebee-android:2.0.0-beta-1' ``` ## Example project @@ -55,9 +61,21 @@ To configure the Chargebee Android SDK for completing and managing In-App Purcha ```kotlin import com.chargebee.android.Chargebee -Chargebee.configure(site= "your-site", - publishableApiKey= "api_key", - sdkKey= "sdk_key",packageName = "packageName") +Chargebee.configure( + site = "your-site", + publishableApiKey = "api-key", + sdkKey = "sdk-key", + packageName = "your-package" +) { + when (it) { + is ChargebeeResult.Success -> { + // Success + } + is ChargebeeResult.Error -> { + // Error + } + } +} ``` ### Configuration for credit card using tokenization To configure SDK only for tokenizing credit card details, follow these steps. @@ -69,7 +87,7 @@ To configure SDK only for tokenizing credit card details, follow these steps. ```kotlin import com.chargebee.android.Chargebee -Chargebee.configure(site = "your-site", publishableApiKey = "api_key") +Chargebee.configure(site = "your-site", publishableApiKey = "api-key") ``` ## Integration @@ -86,7 +104,7 @@ The following section describes how to use the SDK to integrate In-App Purchase Every In-App Purchase subscription product that you configure in your Play Store account, can be configured in Chargebee as a Plan. Start by retrieving the Google IAP Product IDs from your Chargebee account. ```kotlin -CBPurchase.retrieveProductIdentifers(queryParam) { +CBPurchase.retrieveProductIdentifiers(queryParam) { when (it) { is CBProductIDResult.ProductIds -> { Log.i(TAG, "List of Product Identifiers: $it") @@ -106,29 +124,29 @@ The above function will determine your product catalog version in Chargebee and Retrieve the Google IAP Product using the following function. ```kotlin -CBPurchase.retrieveProducts(this, productIdList= "[Product ID's from Google Play Console]", - object : CBCallback.ListProductsCallback> { - override fun onSuccess(productDetails: ArrayList) { +CBPurchase.retrieveProducts(activity, productIdList= ["Product ID's from Google Play Console"], + object : CBCallback.ListProductsCallback> { + override fun onSuccess(productDetails: ArrayList) { Log.i(TAG, "List of Products: $productDetails") } override fun onError(error: CBException) { Log.e(TAG, "Error: ${error.message}") - // Handle error here } }) - ``` You can present any of the above products to your users for them to purchase. ### Buy or Subscribe Product -Pass the `CBProduct` and `CBCustomer` objects to the following function when the user chooses the product to purchase. +Pass the `PurchaseProductParams`, `CBCustomer` and `OfferToken` to the following function when the user chooses the product to purchase. `CBCustomer` - **Optional object**. Although this is an optional object, we recommend passing the necessary customer details, such as `customerId`, `firstName`, `lastName`, and `email` if it is available before the user subscribes to your App. This ensures that the customer details in your database match the customer details in Chargebee. If the `customerId` is not passed in the customer's details, then the value of `customerId` will be the same as the `SubscriptionId` created in Chargebee. **Note**: The `customer` parameter in the below code snippet is an instance of `CBCustomer` class that contains the details of the customer who wants to subscribe or buy the product. ```kotlin -CBPurchase.purchaseProduct(product=CBProduct, customer=CBCustomer, object : CBCallback.PurchaseCallback{ +val purchaseParams = PurchaseProductParams(selectedCBProduct, "selectedOfferToken") +val cbCustomer = CBCustomer("customerId","firstName","lastName","email") +CBPurchase.purchaseProduct(purchaseProductParams = purchaseProductParams, customer = cbCustomer, object : CBCallback.PurchaseCallback{ override fun onSuccess(result: ReceiptDetail, status:Boolean) { Log.i(TAG, "$status") Log.i(TAG, "${result.subscription_id}") @@ -162,7 +180,7 @@ CBPurchase.purchaseNonSubscriptionProduct(product = CBProduct, customer = CBCust The given code defines a function named `purchaseNonSubscriptionProduct` in the CBPurchase class, which takes four input parameters: -- `product`: An instance of `CBProduct` class, initialized with a `SkuDetails` instance representing the product to be purchased from the Google Play Store. +- `product`: An instance of `CBProduct` class, representing the product to be purchased from the Google Play Store. - `customer`: Optional. An instance of `CBCustomer` class, initialized with the customer's details such as `customerId`, `firstName`, `lastName`, and `email`. - `productType`: An enum instance of `productType` type, indicating the type of product to be purchased. It can be either .`consumable`, or `non_consumable`. - `callback`: The `OneTimePurchaseCallback` listener will be invoked when product purchase completes. diff --git a/app/src/main/java/com/chargebee/example/MainActivity.kt b/app/src/main/java/com/chargebee/example/MainActivity.kt index 98750f4..a16cc99 100644 --- a/app/src/main/java/com/chargebee/example/MainActivity.kt +++ b/app/src/main/java/com/chargebee/example/MainActivity.kt @@ -173,11 +173,10 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener { ) && !TextUtils.isEmpty(sdkKeyEditText.text.toString()) ) Chargebee.configure( - siteNameEditText.text.toString(), - apiKeyEditText.text.toString(), - true, - sdkKeyEditText.text.toString(), - this.packageName + site = siteNameEditText.text.toString(), + publishableApiKey = apiKeyEditText.text.toString(), + sdkKey = sdkKeyEditText.text.toString(), + packageName = this.packageName ) { when (it) { is ChargebeeResult.Success -> { diff --git a/app/src/main/java/com/chargebee/example/billing/BillingViewModel.kt b/app/src/main/java/com/chargebee/example/billing/BillingViewModel.kt index 8aa081a..1fdf2ee 100644 --- a/app/src/main/java/com/chargebee/example/billing/BillingViewModel.kt +++ b/app/src/main/java/com/chargebee/example/billing/BillingViewModel.kt @@ -31,7 +31,7 @@ class BillingViewModel : ViewModel() { fun purchaseProduct(context: Context, purchaseProductParams: PurchaseProductParams, customer: CBCustomer) { // Cache the product id in sharedPreferences and retry validating the receipt if in case server is not responding or no internet connection. sharedPreference = context.getSharedPreferences("PREFERENCE_NAME",Context.MODE_PRIVATE) - CBPurchase.purchaseProduct(purchaseProductParams, customer, object : CBCallback.PurchaseCallback{ + CBPurchase.purchaseProduct(purchaseProductParams = purchaseProductParams, customer = customer, object : CBCallback.PurchaseCallback{ override fun onSuccess(result: ReceiptDetail, status:Boolean) { Log.i(TAG, "Subscription ID: ${result.subscription_id}") Log.i(TAG, "Plan ID: ${result.plan_id}") diff --git a/chargebee/src/main/java/com/chargebee/android/billingservice/CBPurchase.kt b/chargebee/src/main/java/com/chargebee/android/billingservice/CBPurchase.kt index e5c41f6..079a2e0 100644 --- a/chargebee/src/main/java/com/chargebee/android/billingservice/CBPurchase.kt +++ b/chargebee/src/main/java/com/chargebee/android/billingservice/CBPurchase.kt @@ -55,7 +55,7 @@ object CBPurchase { /** * Buy Subscription product with/without customer data - * @param [purchaseParams] The purchase parameters of the product to be purchased. + * @param [purchaseProductParams] The purchase parameters of the product to be purchased. * @param [customer] Optional. Customer Object. * @param [callback] listener will be called when product purchase completes. */ From ed3513f9c9eb39ab1bcbb5a2fb2ed23fce02d5d8 Mon Sep 17 00:00:00 2001 From: haripriyan Date: Fri, 24 Nov 2023 22:49:18 +0530 Subject: [PATCH 09/11] Updates version --- chargebee/src/main/java/com/chargebee/android/Chargebee.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chargebee/src/main/java/com/chargebee/android/Chargebee.kt b/chargebee/src/main/java/com/chargebee/android/Chargebee.kt index 785de24..a7995a6 100644 --- a/chargebee/src/main/java/com/chargebee/android/Chargebee.kt +++ b/chargebee/src/main/java/com/chargebee/android/Chargebee.kt @@ -35,7 +35,7 @@ object Chargebee { var appName: String = "Chargebee" var environment: String = "cb_android_sdk" const val platform: String = "Android" - const val sdkVersion: String = "2.0.0" + const val sdkVersion: String = "2.0.0-beta-1" const val limit: String = "100" private const val PLAY_STORE_SUBSCRIPTION_URL = "https://play.google.com/store/account/subscriptions" From 8ec2e4a421505908841d6d6b73e8957aa16a9184 Mon Sep 17 00:00:00 2001 From: haripriyan Date: Mon, 27 Nov 2023 19:28:28 +0530 Subject: [PATCH 10/11] Updates readme --- README.md | 8 +++++--- .../main/java/com/chargebee/example/ExampleApplication.kt | 1 - .../com/chargebee/example/billing/BillingActivity.java | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 706cc2d..afd7ef2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # Chargebee Android > #### Updates for Billing Library 5 -> ***Note**: If you want to simply update your app to use Google Billing Library 5, without having to make related changes, you can use the [1.x version](https://github.com/chargebee/chargebee-android/tree/1.x.x). You can upgrade to the latest version once you are ready. -> -> If you are getting started with the integration or ready to leverage Google Billing Library 5 changes, follow this [link](https://www.chargebee.com/docs/2.0/mobile-playstore-billing-library-5.html) for getting started. +> ***Note**: +> - SDK Version 2.0: This version uses Google Billing Library 5.2.1 APIs to fetch product information from the Google Play Console and make purchases. If you’re integrating Chargebee’s SDK for the first time, then use this version, and if you’re migrating from the older version of SDK to this version, follow the migration steps in this [document](https://www.chargebee.com/docs/2.0/mobile-playstore-billing-library-5.html). +> - SDK Version 1.2.0: This version includes Billing Library 5.2.1 but still uses Billing Library 4.0 APIs to fetch product information from the Google Play Console and make purchases. This will enable you to list or update your Android app on the store without any warnings from Google and give you enough time to migrate to version 2.0. +> - SDK Version 1.1.0: This and less than this version of SDKs use billing library 4.0 APIs that are deprecated by Google. Therefore, it is highly recommended that you upgrade your app and integrate it with SDK version 1.2.0 and above. + This is Chargebee’s Android Software Development Kit (SDK). This SDK makes it efficient and comfortable to build a seamless subscription experience in your Android app. diff --git a/app/src/main/java/com/chargebee/example/ExampleApplication.kt b/app/src/main/java/com/chargebee/example/ExampleApplication.kt index 0dae9b5..fc678ac 100644 --- a/app/src/main/java/com/chargebee/example/ExampleApplication.kt +++ b/app/src/main/java/com/chargebee/example/ExampleApplication.kt @@ -2,7 +2,6 @@ package com.chargebee.example import android.app.Application import android.content.Context -import com.chargebee.android.Chargebee import android.content.SharedPreferences import android.util.Log import com.chargebee.android.billingservice.CBCallback diff --git a/app/src/main/java/com/chargebee/example/billing/BillingActivity.java b/app/src/main/java/com/chargebee/example/billing/BillingActivity.java index 4c2c843..231ffa4 100644 --- a/app/src/main/java/com/chargebee/example/billing/BillingActivity.java +++ b/app/src/main/java/com/chargebee/example/billing/BillingActivity.java @@ -163,7 +163,7 @@ private void getCustomerID() { dialog.dismiss(); } } else { - purchaseProduct(customerId); + purchaseProduct(); dialog.dismiss(); } }); @@ -182,7 +182,7 @@ private boolean isOneTimeProduct(){ return purchaseProducts.get(position).getCbProduct().getType() == ProductType.INAPP; } - private void purchaseProduct(String customerId) { + private void purchaseProduct() { showProgressDialog(); PurchaseProduct selectedPurchaseProduct = purchaseProducts.get(position); PurchaseProductParams purchaseParams = new PurchaseProductParams(selectedPurchaseProduct.getCbProduct(), selectedPurchaseProduct.getOfferToken()); From 015435bce20d8f0ad51b87dc84f75964ca76435a Mon Sep 17 00:00:00 2001 From: haripriyan Date: Mon, 27 Nov 2023 20:18:48 +0530 Subject: [PATCH 11/11] Updates readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index afd7ef2..a7012f6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ > #### Updates for Billing Library 5 > ***Note**: > - SDK Version 2.0: This version uses Google Billing Library 5.2.1 APIs to fetch product information from the Google Play Console and make purchases. If you’re integrating Chargebee’s SDK for the first time, then use this version, and if you’re migrating from the older version of SDK to this version, follow the migration steps in this [document](https://www.chargebee.com/docs/2.0/mobile-playstore-billing-library-5.html). -> - SDK Version 1.2.0: This version includes Billing Library 5.2.1 but still uses Billing Library 4.0 APIs to fetch product information from the Google Play Console and make purchases. This will enable you to list or update your Android app on the store without any warnings from Google and give you enough time to migrate to version 2.0. +> - SDK Version 1.2.0: This [version](https://github.com/chargebee/chargebee-android/tree/1.x.x) includes Billing Library 5.2.1 but still uses Billing Library 4.0 APIs to fetch product information from the Google Play Console and make purchases. This will enable you to list or update your Android app on the store without any warnings from Google and give you enough time to migrate to version 2.0. > - SDK Version 1.1.0: This and less than this version of SDKs use billing library 4.0 APIs that are deprecated by Google. Therefore, it is highly recommended that you upgrade your app and integrate it with SDK version 1.2.0 and above.