diff --git a/app/src/main/java/com/chargebee/example/MainActivity.kt b/app/src/main/java/com/chargebee/example/MainActivity.kt index d98d565..1987cfd 100644 --- a/app/src/main/java/com/chargebee/example/MainActivity.kt +++ b/app/src/main/java/com/chargebee/example/MainActivity.kt @@ -73,6 +73,15 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener { Log.i(javaClass.simpleName, "Google play product identifiers: $it") alertListProductId(it) } + + this.mBillingViewModel!!.restorePurchaseResult.observeForever { + hideProgressDialog() + if (it.isNotEmpty()) { + alertSuccess("${it.size} purchases restored successfully") + } else { + alertSuccess("Purchases not found to restore") + } + } } private fun setListAdapter() { @@ -133,7 +142,7 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener { getSubscriptionId() } CBMenu.RestorePurchase.value -> { - restorePurchases() + mBillingViewModel?.restorePurchases(this) } else -> { Log.i(javaClass.simpleName, " Not implemented") @@ -208,34 +217,6 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener { }) } - private fun restorePurchases() { - showProgressDialog() - CBPurchase.restorePurchases( - context = this, includeInActivePurchases = true, - completionCallback = object : CBCallback.RestorePurchaseCallback { - override fun onSuccess(result: List) { - hideProgressDialog() - result.forEach { - Log.i(javaClass.simpleName, "status : ${it.storeStatus}") - } - CoroutineScope(Dispatchers.Main).launch { - if (result.isNotEmpty()) - alertSuccess("${result.size} purchases restored successfully") - else - alertSuccess("Purchases not found to restore") - } - } - - override fun onError(error: CBException) { - hideProgressDialog() - Log.e(javaClass.simpleName, "error message: ${error.message}") - CoroutineScope(Dispatchers.Main).launch { - showDialog("${error.message}, ${error.httpStatusCode}") - } - } - }) - } - private fun alertListProductId(list: Array) { val builder = AlertDialog.Builder(this) builder.setTitle("Chargebee Product IDs") 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 932d568..a5678b8 100644 --- a/app/src/main/java/com/chargebee/example/billing/BillingViewModel.kt +++ b/app/src/main/java/com/chargebee/example/billing/BillingViewModel.kt @@ -28,6 +28,7 @@ class BillingViewModel : ViewModel() { var entitlementsResult: MutableLiveData = MutableLiveData() private var subscriptionId: String = "" private lateinit var sharedPreference : SharedPreferences + var restorePurchaseResult: MutableLiveData> = MutableLiveData() fun purchaseProduct(context: Context,product: CBProduct, customer: CBCustomer) { // Cache the product id in sharedPreferences and retry validating the receipt if in case server is not responding or no internet connection. @@ -181,4 +182,21 @@ class BillingViewModel : ViewModel() { editor.putString("productId", productId) editor.apply() } + + fun restorePurchases(context: Context, includeInActivePurchases: Boolean = false) { + CBPurchase.restorePurchases( + context = context, includeInActivePurchases = includeInActivePurchases, + completionCallback = object : CBCallback.RestorePurchaseCallback { + override fun onSuccess(result: List) { + result.forEach { + Log.i(javaClass.simpleName, "status : ${it.storeStatus}") + } + restorePurchaseResult.postValue(result) + } + + override fun onError(error: CBException) { + cbException.postValue(error) + } + }) + } } \ No newline at end of file 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 777600f..919f23e 100644 --- a/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt +++ b/chargebee/src/main/java/com/chargebee/android/billingservice/BillingClientManager.kt @@ -13,6 +13,7 @@ import com.chargebee.android.models.PurchaseTransaction import com.chargebee.android.exceptions.CBException import com.chargebee.android.exceptions.ChargebeeResult import com.chargebee.android.models.CBProduct +import com.chargebee.android.models.StoreStatus import com.chargebee.android.network.CBReceiptResponse import com.chargebee.android.restore.CBRestorePurchaseManager import kotlin.collections.ArrayList @@ -309,10 +310,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { queryPurchaseHistory { purchaseHistoryList -> val storeTransactions = arrayListOf() storeTransactions.addAll(purchaseHistoryList) - CBRestorePurchaseManager.fetchStoreSubscriptionStatus( - storeTransactions, - restorePurchaseCallBack - ) + fetchStoreSubscriptionStatus(storeTransactions) } } else { restorePurchaseCallBack.onError( @@ -321,6 +319,59 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { } } + internal fun fetchStoreSubscriptionStatus(storeTransactions: ArrayList){ + CBRestorePurchaseManager.fetchStoreSubscriptionStatus(storeTransactions, result = { restorePurchases -> + val activePurchases = restorePurchases.filter { subscription -> + subscription.storeStatus == StoreStatus.Active.value + } + if (CBPurchase.includeInActivePurchases) { + restorePurchaseCallBack.onSuccess(restorePurchases) + syncPurchaseWithChargebee(CBRestorePurchaseManager.allTransactions) + } else { + restorePurchaseCallBack.onSuccess(activePurchases) + syncPurchaseWithChargebee(CBRestorePurchaseManager.activeTransactions) + } + }, error = { + restorePurchaseCallBack.onError(error = it) + }) + } + + internal fun syncPurchaseWithChargebee(storeTransactions: ArrayList) { + storeTransactions.forEach { productIdList -> + retrieveProducts( + CBPurchase.ProductType.SUBS.value, + ArrayList(productIdList.productId), + object : CBCallback.ListProductsCallback> { + override fun onSuccess(productIDs: ArrayList) { + if (productIDs.size == 0) { + Log.i(javaClass.simpleName, "Product not available") + return + } + CBPurchase.validateReceipt( + productIdList.purchaseToken, + productIDs.first() + ) { + when (it) { + is ChargebeeResult.Success -> { + Log.i(javaClass.simpleName, "result : ${it.data}") + } + is ChargebeeResult.Error -> { + Log.e( + javaClass.simpleName, + "Exception from Server - validateReceipt() : ${it.exp.message}" + ) + } + } + } + } + + override fun onError(error: CBException) { + Log.e(javaClass.simpleName, "Error: ${error.message}") + } + }) + } + } + private fun queryPurchaseHistory( storeTransactions: (List) -> Unit ) { 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 595a477..ff1401e 100644 --- a/chargebee/src/main/java/com/chargebee/android/billingservice/CBPurchase.kt +++ b/chargebee/src/main/java/com/chargebee/android/billingservice/CBPurchase.kt @@ -19,6 +19,9 @@ object CBPurchase { val productIdList = arrayListOf() private var customer: CBCustomer? = null internal var includeInActivePurchases = false + private var offerDetail: OfferDetail? = null + private lateinit var introductoryOfferType: OfferType + private var numberOfUnits: Int = 0 internal enum class ProductType(val value: String) { SUBS("subs"), @@ -168,7 +171,8 @@ object CBPurchase { completion: (ChargebeeResult) -> Unit ) { try { - validateReceipt(purchaseToken, product.productId, completion) + val offerDetail = getOfferDetail(product) + validateReceipt(purchaseToken, product.productId, offerDetail, completion) } catch (exp: Exception) { Log.e(javaClass.simpleName, "Exception in validateReceipt() :" + exp.message) ChargebeeResult.Error( @@ -184,6 +188,7 @@ object CBPurchase { internal fun validateReceipt( purchaseToken: String, productId: String, + offerDetail: OfferDetail?, completion: (ChargebeeResult) -> Unit ) { val logger = CBLogger(name = "buy", action = "process_purchase_command") @@ -191,7 +196,8 @@ object CBPurchase { purchaseToken, productId, customer, - Chargebee.channel + Chargebee.channel, + offerDetail ) ResultHandler.safeExecuter( { ReceiptResource().validateReceipt(params) }, @@ -301,4 +307,30 @@ object CBPurchase { } return billingClientManager as BillingClientManager } + + private fun convertIntroductoryPriceAmountInMicros(product: CBProduct): Long { + return product.skuDetails.introductoryPriceAmountMicros / 1_000_0 + } + + private fun getOfferDetail(product: CBProduct): OfferDetail? { + if (product.skuDetails.introductoryPrice.isNotEmpty()) { + val subscriptionPeriod = product.skuDetails.introductoryPricePeriod + val introductoryPriceAmountMicros = convertIntroductoryPriceAmountInMicros(product) + if (product.skuDetails.introductoryPriceCycles == 1) { + introductoryOfferType = OfferType.PAY_UP_FRONT + numberOfUnits = + subscriptionPeriod.substring(1, subscriptionPeriod.length - 1).toInt() + } else { + introductoryOfferType = OfferType.PAY_AS_YOU_GO + numberOfUnits = product.skuDetails.introductoryPriceCycles + } + offerDetail = OfferDetail( + introductoryPrice = product.skuDetails.introductoryPrice, + introductoryPriceAmountMicros = introductoryPriceAmountMicros, + introductoryPricePeriod = numberOfUnits, + introductoryOfferType = introductoryOfferType + ) + } + return offerDetail + } } \ No newline at end of file diff --git a/chargebee/src/main/java/com/chargebee/android/models/OfferDetail.kt b/chargebee/src/main/java/com/chargebee/android/models/OfferDetail.kt new file mode 100644 index 0000000..12a1d8c --- /dev/null +++ b/chargebee/src/main/java/com/chargebee/android/models/OfferDetail.kt @@ -0,0 +1,13 @@ +package com.chargebee.android.models + +data class OfferDetail( + val introductoryPrice: String, + val introductoryPriceAmountMicros: Long, + val introductoryPricePeriod: Int, + val introductoryOfferType: OfferType +) + +enum class OfferType(val value: String) { + PAY_UP_FRONT("pay_up_front"), + PAY_AS_YOU_GO("pay_as_you_go"); +} diff --git a/chargebee/src/main/java/com/chargebee/android/network/CBReceiptRequestBody.kt b/chargebee/src/main/java/com/chargebee/android/network/CBReceiptRequestBody.kt index 470b626..b5f57bd 100644 --- a/chargebee/src/main/java/com/chargebee/android/network/CBReceiptRequestBody.kt +++ b/chargebee/src/main/java/com/chargebee/android/network/CBReceiptRequestBody.kt @@ -1,16 +1,20 @@ package com.chargebee.android.network +import com.chargebee.android.models.OfferDetail + internal class CBReceiptRequestBody( val receipt: String, val productId: String, val customer: CBCustomer?, - val channel: String) { + val channel: String, + val offerDetail: OfferDetail?) { companion object { fun fromCBReceiptReqBody(params: Params): CBReceiptRequestBody { return CBReceiptRequestBody( params.receipt, params.productId, params.customer, - params.channel + params.channel, + params.offerDetail ) } } @@ -25,7 +29,7 @@ internal class CBReceiptRequestBody( val receipt: String, } fun toCBReceiptReqCustomerBody(): Map { - return mapOf( + val queryMap = mutableMapOf( "receipt" to this.receipt, "product[id]" to this.productId, "customer[id]" to this.customer?.id, @@ -34,13 +38,24 @@ internal class CBReceiptRequestBody( val receipt: String, "customer[email]" to this.customer?.email, "channel" to this.channel ) + return getOfferParams(queryMap) } - fun toMap(): Map { - return mapOf( + fun toMap(): Map { + val queryMap = mutableMapOf( "receipt" to this.receipt, "product[id]" to this.productId, "channel" to this.channel ) + return getOfferParams(queryMap) + } + + private fun getOfferParams(queryMap: MutableMap) : Map{ + if (offerDetail != null) { + queryMap["introductory_offer[price]"] = this.offerDetail.introductoryPriceAmountMicros.toString() + queryMap["introductory_offer[type]"] = this.offerDetail.introductoryOfferType.value + queryMap["introductory_offer[period]"] = this.offerDetail.introductoryPricePeriod.toString() + } + return queryMap } } @@ -48,7 +63,8 @@ data class Params( val receipt: String, val productId: String, val customer: CBCustomer?, - val channel: String + val channel: String, + val offerDetail: OfferDetail? ) data class CBCustomer( val id: String?, diff --git a/chargebee/src/main/java/com/chargebee/android/resources/ReceiptResource.kt b/chargebee/src/main/java/com/chargebee/android/resources/ReceiptResource.kt index 07f4921..fc0e84e 100644 --- a/chargebee/src/main/java/com/chargebee/android/resources/ReceiptResource.kt +++ b/chargebee/src/main/java/com/chargebee/android/resources/ReceiptResource.kt @@ -12,9 +12,8 @@ import com.chargebee.android.responseFromServer internal class ReceiptResource : BaseResource(baseUrl = Chargebee.baseUrl){ internal suspend fun validateReceipt(params: Params): ChargebeeResult { - var dataMap = mapOf() val paramDetail = CBReceiptRequestBody.fromCBReceiptReqBody(params) - dataMap = if (params.customer != null && !(TextUtils.isEmpty(params.customer.id))) { + val dataMap = if (params.customer != null && !(TextUtils.isEmpty(params.customer.id))) { paramDetail.toCBReceiptReqCustomerBody() } else{ paramDetail.toMap() diff --git a/chargebee/src/main/java/com/chargebee/android/restore/CBRestorePurchaseManager.kt b/chargebee/src/main/java/com/chargebee/android/restore/CBRestorePurchaseManager.kt index 1baced3..a392249 100644 --- a/chargebee/src/main/java/com/chargebee/android/restore/CBRestorePurchaseManager.kt +++ b/chargebee/src/main/java/com/chargebee/android/restore/CBRestorePurchaseManager.kt @@ -1,5 +1,6 @@ package com.chargebee.android.restore +import android.content.Context import android.util.Log import com.chargebee.android.ErrorDetail import com.chargebee.android.billingservice.CBCallback @@ -15,9 +16,9 @@ import com.chargebee.android.resources.RestorePurchaseResource class CBRestorePurchaseManager { companion object { - private var allTransactions = ArrayList() + internal var allTransactions = ArrayList() private var restorePurchases = ArrayList() - private var activeTransactions = ArrayList() + internal var activeTransactions = ArrayList() private lateinit var completionCallback: CBCallback.RestorePurchaseCallback private fun retrieveStoreSubscription( @@ -55,9 +56,9 @@ class CBRestorePurchaseManager { internal fun fetchStoreSubscriptionStatus( storeTransactions: ArrayList, - completionCallback: CBCallback.RestorePurchaseCallback + result: (List) -> Unit, + error: (CBException) -> Unit ) { - this.completionCallback = completionCallback if (storeTransactions.isNotEmpty()) { val storeTransaction = storeTransactions.firstOrNull()?.also { storeTransactions.remove(it) } @@ -65,23 +66,30 @@ class CBRestorePurchaseManager { retrieveRestoreSubscription(purchaseToken, { restorePurchases.add(it) when (it.storeStatus) { - StoreStatus.Active.value -> activeTransactions.add(storeTransaction) + StoreStatus.Active.value -> { + activeTransactions.add(storeTransaction) + allTransactions.add(storeTransaction) + } else -> allTransactions.add(storeTransaction) } - getRestorePurchases(storeTransactions) + getRestorePurchases(storeTransactions, result, error) }, { _ -> - getRestorePurchases(storeTransactions) + getRestorePurchases(storeTransactions, result, error) }) } } else { - completionCallback.onSuccess(emptyList()) + result(emptyList()) } } - internal fun getRestorePurchases(storeTransactions: ArrayList) { + internal fun getRestorePurchases( + storeTransactions: ArrayList, + result: (List) -> Unit, + error: (CBException) -> Unit + ) { if (storeTransactions.isEmpty()) { if (restorePurchases.isEmpty()) { - completionCallback.onError( + error( CBException( ErrorDetail( message = GPErrorCode.InvalidPurchaseToken.errorMsg, @@ -90,46 +98,13 @@ class CBRestorePurchaseManager { ) ) } else { - val activePurchases = restorePurchases.filter { subscription -> - subscription.storeStatus == StoreStatus.Active.value - } - val allPurchases = restorePurchases.filter { subscription -> - subscription.storeStatus == StoreStatus.Active.value || subscription.storeStatus == StoreStatus.InTrial.value - || subscription.storeStatus == StoreStatus.Cancelled.value || subscription.storeStatus == StoreStatus.Paused.value - } - if (CBPurchase.includeInActivePurchases) { - completionCallback.onSuccess(allPurchases) - syncPurchaseWithChargebee(allTransactions) - } else { - completionCallback.onSuccess(activePurchases) - syncPurchaseWithChargebee(activeTransactions) - } + result(restorePurchases) } restorePurchases.clear() + allTransactions.clear() + activeTransactions.clear() } else { - fetchStoreSubscriptionStatus(storeTransactions, completionCallback) - } - } - - internal fun syncPurchaseWithChargebee(storeTransactions: ArrayList) { - storeTransactions.forEach { productIdList -> - validateReceipt(productIdList.purchaseToken, productIdList.productId.first()) - } - } - - internal fun validateReceipt(purchaseToken: String, productId: String) { - CBPurchase.validateReceipt(purchaseToken, productId) { - when (it) { - is ChargebeeResult.Success -> { - Log.i(javaClass.simpleName, "result : ${it.data}") - } - is ChargebeeResult.Error -> { - Log.e( - javaClass.simpleName, - "Exception from Server - validateReceipt() : ${it.exp.message}" - ) - } - } + fetchStoreSubscriptionStatus(storeTransactions, result, error) } } } 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 b017906..be19de9 100644 --- a/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt +++ b/chargebee/src/test/java/com/chargebee/android/billingservice/BillingClientManagerTest.kt @@ -11,6 +11,7 @@ import com.chargebee.android.exceptions.CBException import com.chargebee.android.exceptions.CBProductIDResult import com.chargebee.android.exceptions.ChargebeeResult import com.chargebee.android.models.CBProduct +import com.chargebee.android.models.OfferDetail import com.chargebee.android.network.* import com.chargebee.android.resources.CatalogVersion import com.chargebee.android.resources.ReceiptResource @@ -49,11 +50,16 @@ class BillingClientManagerTest { private var customer = CBCustomer("test", "android", "test", "test@gmail.com") private var customerId: String = "test" private val purchaseToken = "56sadmnagdjsd" + private val offerDetail = OfferDetail( introductoryPrice = "", + introductoryPriceAmountMicros = 0, + introductoryPricePeriod = 0, + introductoryOfferType = "") private val params = Params( "purchaseToken", "product.productId", customer, - Chargebee.channel + Chargebee.channel, + offerDetail ) private val receiptDetail = ReceiptDetail("subscriptionId", "customerId", "planId") @@ -300,7 +306,7 @@ class BillingClientManagerTest { val purchaseToken = "56sadmnagdjsd" val lock = CountDownLatch(1) CoroutineScope(Dispatchers.IO).launch { - CBPurchase.validateReceipt(purchaseToken, "productId") { + CBPurchase.validateReceipt(purchaseToken, productId = "productId", offerDetail = offerDetail) { when (it) { is ChargebeeResult.Success -> { lock.countDown() @@ -322,14 +328,14 @@ class BillingClientManagerTest { ) ) verify(ReceiptResource(), times(1)).validateReceipt(params) - verify(CBReceiptRequestBody("receipt","",null,""), times(1)).toCBReceiptReqBody() + verify(CBReceiptRequestBody("receipt","",null,"", offerDetail), times(1)).toCBReceiptReqBody() } } @Test fun test_validateReceipt_error(){ val purchaseToken = "56sadmnagdjsd" CoroutineScope(Dispatchers.IO).launch { - CBPurchase.validateReceipt(purchaseToken, "products") { + CBPurchase.validateReceipt(purchaseToken, productId = "productId", offerDetail = offerDetail){ when (it) { is ChargebeeResult.Success -> { assertThat(it, instanceOf(CBReceiptResponse::class.java)) @@ -348,7 +354,7 @@ class BillingClientManagerTest { ) ) verify(ReceiptResource(), times(1)).validateReceipt(params) - verify(CBReceiptRequestBody("receipt","",null,""), times(1)).toCBReceiptReqBody() + verify(CBReceiptRequestBody("receipt","",null,"", offerDetail), times(1)).toCBReceiptReqBody() } } @@ -416,6 +422,7 @@ class BillingClientManagerTest { } lock.await() } + @Test fun test_purchaseProductWithCBCustomer_error(){ val jsonDetails = "{\"productId\":\"merchant.premium.android\",\"type\":\"subs\",\"title\":\"Premium Plan (Chargebee Example)\",\"name\":\"Premium Plan\",\"price\":\"₹2,650.00\",\"price_amount_micros\":2650000000,\"price_currency_code\":\"INR\",\"description\":\"Every 6 Months\",\"subscriptionPeriod\":\"P6M\",\"skuDetailsToken\":\"AEuhp4J0KiD1Bsj3Yq2mHPBRNHUBdzs4nTJY3PWRR8neE-22MJNssuDzH2VLFKv35Ov8\"}" @@ -442,10 +449,7 @@ class BillingClientManagerTest { fun test_validateReceiptWithChargebee_success() { val response = CBReceiptResponse(receiptDetail) CoroutineScope(Dispatchers.IO).launch { - CBPurchase.validateReceipt( - purchaseToken = "purchaseToken", - productId = "productId" - ) { + CBPurchase.validateReceipt(purchaseToken, productId = "productId", offerDetail = offerDetail) { when (it) { is ChargebeeResult.Success -> { assertThat(it, instanceOf(ReceiptDetail::class.java)) @@ -463,7 +467,7 @@ class BillingClientManagerTest { ) ) verify(ReceiptResource(), times(1)).validateReceipt(params) - verify(CBReceiptRequestBody("receipt", "", null, ""), times(1)).toCBReceiptReqBody() + verify(CBReceiptRequestBody("receipt", "", null, "", offerDetail), times(1)).toCBReceiptReqBody() } } @@ -472,10 +476,7 @@ class BillingClientManagerTest { fun test_validateReceiptWithChargebee_error() { val exception = CBException(ErrorDetail("Error")) CoroutineScope(Dispatchers.IO).launch { - CBPurchase.validateReceipt( - purchaseToken = "purchaseToken", - productId = "productId" - ) { + CBPurchase.validateReceipt(purchaseToken, productId = "productId", offerDetail = offerDetail) { when (it) { is ChargebeeResult.Success -> { assertThat(it, instanceOf(ReceiptDetail::class.java)) @@ -493,7 +494,54 @@ class BillingClientManagerTest { ) ) verify(ReceiptResource(), times(1)).validateReceipt(params) - verify(CBReceiptRequestBody("receipt", "", null, ""), times(1)).toCBReceiptReqBody() + verify(CBReceiptRequestBody("receipt", "", null, "", offerDetail), times(1)).toCBReceiptReqBody() + } + } + + @Test + fun test_syncPurchaseWithChargebee_success() { + val purchaseTransaction = TestData.getTransaction(true) + val params = Params( + purchaseTransaction.first().purchaseToken, + purchaseTransaction.first().productId.first(), + customer, + Chargebee.channel, + offerDetail + ) + billingClientManager?.syncPurchaseWithChargebee(purchaseTransaction) + CoroutineScope(Dispatchers.IO).launch { + Mockito.`when`(params.let { ReceiptResource().validateReceipt(it) }).thenReturn( + ChargebeeResult.Success( + TestData.response + ) + ) + Mockito.verify(ReceiptResource(), Mockito.times(1)).validateReceipt(params) + Mockito.verify(CBReceiptRequestBody("receipt", "", null, "", offerDetail), Mockito.times(1)) + .toCBReceiptReqBody() } } + + @Test + fun test_syncPurchaseWithChargebee_failure() { + val purchaseTransaction = TestData.getTransaction(false) + val params = Params( + purchaseTransaction.first().purchaseToken, + purchaseTransaction.first().productId.first(), + customer, + Chargebee.channel, + offerDetail + ) + billingClientManager?.syncPurchaseWithChargebee(purchaseTransaction) + CoroutineScope(Dispatchers.IO).launch { + Mockito.`when`(params.let { ReceiptResource().validateReceipt(it) }).thenReturn( + ChargebeeResult.Error( + TestData.error + ) + ) + Mockito.verify(ReceiptResource(), Mockito.times(1)).validateReceipt(params) + Mockito.verify(CBReceiptRequestBody("receipt", "", null, "", offerDetail), Mockito.times(1)) + .toCBReceiptReqBody() + } + } + } \ No newline at end of file diff --git a/chargebee/src/test/java/com/chargebee/android/billingservice/TestData.kt b/chargebee/src/test/java/com/chargebee/android/billingservice/TestData.kt new file mode 100644 index 0000000..3b5feb7 --- /dev/null +++ b/chargebee/src/test/java/com/chargebee/android/billingservice/TestData.kt @@ -0,0 +1,41 @@ +package com.chargebee.android.billingservice + +import com.chargebee.android.ErrorDetail +import com.chargebee.android.exceptions.CBException +import com.chargebee.android.models.PurchaseTransaction +import com.chargebee.android.network.CBReceiptResponse +import com.chargebee.android.network.ReceiptDetail + +object TestData { + private val list = ArrayList() + private val storeTransactions = arrayListOf() + internal val response = + CBReceiptResponse(ReceiptDetail("subscriptionId", "customerId", "planId")) + internal val error = CBException( + ErrorDetail( + message = "The Token data sent is not correct or Google service is temporarily down", + httpStatusCode = 400 + ) + ) + + fun getTransaction(isTestingSuccess: Boolean): ArrayList { + list.add("chargebee.pro.android") + storeTransactions.clear() + val result = if (isTestingSuccess) + PurchaseTransaction( + productId = list.toList(), + purchaseTime = 1682666112774, + purchaseToken = "fajeooclbamgohgapjeehghm.AO-J1OzxVvoEx7y53c9DsypEKwgcfGw2OrisyQsQ-MG6KiXfJ97nT33Yd5VpbQYxd225QnTAEVdPuLP4YSvZE6LBhsv1rzSlizuBxBTjBWghWguSBBtgp2g", + productType = "subs" + ) + else + PurchaseTransaction( + productId = list.toList(), + purchaseTime = 1682666112774, + purchaseToken = "test data", + productType = "subs" + ) + storeTransactions.add(result) + return storeTransactions + } +} \ No newline at end of file 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 2111dca..1c21488 100644 --- a/chargebee/src/test/java/com/chargebee/android/restore/RestorePurchaseTest.kt +++ b/chargebee/src/test/java/com/chargebee/android/restore/RestorePurchaseTest.kt @@ -2,13 +2,11 @@ package com.chargebee.android.restore 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.TestData import com.chargebee.android.exceptions.CBException import com.chargebee.android.exceptions.ChargebeeResult import com.chargebee.android.models.* import com.chargebee.android.network.* -import com.chargebee.android.resources.ReceiptResource import com.chargebee.android.resources.RestorePurchaseResource import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -33,14 +31,7 @@ class RestorePurchaseTest { private val list = ArrayList() private val storeTransactions = arrayListOf() private val lock = CountDownLatch(1) - private val response = - CBReceiptResponse(ReceiptDetail("subscriptionId", "customerId", "planId")) - private val error = CBException( - ErrorDetail( - message = "The Token data sent is not correct or Google service is temporarily down", - httpStatusCode = 400 - ) - ) + @Before fun setUp() { @@ -67,48 +58,39 @@ class RestorePurchaseTest { fun test_fetchStoreSubscriptionStatus_success() { val lock = CountDownLatch(1) val purchaseTransaction = getTransaction(true) - CBRestorePurchaseManager.fetchStoreSubscriptionStatus( purchaseTransaction, - completionCallback = object : RestorePurchaseCallback { - override fun onSuccess(result: List) { - lock.countDown() - result.forEach { - MatcherAssert.assertThat( - (it), - Matchers.instanceOf(CBRestoreSubscription::class.java) - ) - } - - } - - override fun onError(error: CBException) { - lock.countDown() + result = { restorePurchases -> + lock.countDown() + restorePurchases.forEach { MatcherAssert.assertThat( - error, - Matchers.instanceOf(CBException::class.java) + (it), + Matchers.instanceOf(CBRestoreSubscription::class.java) ) } + }, error = { + lock.countDown() }) + CoroutineScope(Dispatchers.IO).launch { + Mockito.verify(RestorePurchaseResource(), Mockito.times(1)) + .retrieveStoreSubscription(purchaseTransaction.first().purchaseToken) + } lock.await() } @Test fun test_fetchStoreSubscriptionStatus_failure() { val purchaseTransaction = getTransaction(false) - - val storeTransaction = - purchaseTransaction.firstOrNull()?.also { purchaseTransaction.remove(it) } - storeTransaction?.purchaseToken?.let { purchaseToken -> - CBRestorePurchaseManager.retrieveRestoreSubscription(purchaseToken, {}, { error -> - lock.countDown() - MatcherAssert.assertThat( - (error), - Matchers.instanceOf(CBException::class.java) - ) - Mockito.verify(CBRestorePurchaseManager, Mockito.times(1)) - .getRestorePurchases(purchaseTransaction) - }) + CBRestorePurchaseManager.fetchStoreSubscriptionStatus(purchaseTransaction, {}, { error -> + lock.countDown() + MatcherAssert.assertThat( + (error), + Matchers.instanceOf(CBException::class.java) + ) + }) + CoroutineScope(Dispatchers.IO).launch { + Mockito.verify(RestorePurchaseResource(), Mockito.times(1)) + .retrieveStoreSubscription(purchaseTransaction.first().purchaseToken) } lock.await() } @@ -140,7 +122,7 @@ class RestorePurchaseTest { Mockito.`when`(RestorePurchaseResource().retrieveStoreSubscription(purchaseToken)) .thenReturn( ChargebeeResult.Error( - error + TestData.error ) ) Mockito.verify(RestorePurchaseResource(), Mockito.times(1)) @@ -148,96 +130,6 @@ class RestorePurchaseTest { } } - @Test - fun test_validateReceipt_success() { - val purchaseTransaction = getTransaction(true) - val params = Params( - purchaseTransaction.first().purchaseToken, - purchaseTransaction.first().productId.first(), - customer, - Chargebee.channel - ) - CBRestorePurchaseManager.validateReceipt( - params.receipt, - purchaseTransaction.first().productType - ) - CoroutineScope(Dispatchers.IO).launch { - Mockito.`when`(params.let { ReceiptResource().validateReceipt(it) }).thenReturn( - ChargebeeResult.Success( - response - ) - ) - Mockito.verify(ReceiptResource(), Mockito.times(1)).validateReceipt(params) - Mockito.verify(CBReceiptRequestBody("receipt", "", null, ""), Mockito.times(1)) - .toCBReceiptReqBody() - } - } - - @Test - fun test_validateReceipt_failure() { - val purchaseTransaction = getTransaction(false) - val params = Params( - purchaseTransaction.first().purchaseToken, - purchaseTransaction.first().productId.first(), - customer, - Chargebee.channel - ) - CoroutineScope(Dispatchers.IO).launch { - Mockito.`when`(params.let { ReceiptResource().validateReceipt(it) }).thenReturn( - ChargebeeResult.Error( - error - ) - ) - Mockito.verify(ReceiptResource(), Mockito.times(1)).validateReceipt(params) - Mockito.verify(CBReceiptRequestBody("receipt", "", null, ""), Mockito.times(1)) - .toCBReceiptReqBody() - } - } - - @Test - fun test_syncPurchaseWithChargebee_success() { - val purchaseTransaction = getTransaction(false) - val params = Params( - purchaseTransaction.first().purchaseToken, - purchaseTransaction.first().productId.first(), - customer, - Chargebee.channel - ) - CBRestorePurchaseManager.syncPurchaseWithChargebee(purchaseTransaction) - CoroutineScope(Dispatchers.IO).launch { - Mockito.`when`(params.let { ReceiptResource().validateReceipt(it) }).thenReturn( - ChargebeeResult.Success( - response - ) - ) - Mockito.verify(ReceiptResource(), Mockito.times(1)).validateReceipt(params) - Mockito.verify(CBReceiptRequestBody("receipt", "", null, ""), Mockito.times(1)) - .toCBReceiptReqBody() - } - } - - @Test - fun test_syncPurchaseWithChargebee_failure() { - val purchaseTransaction = getTransaction(false) - val params = Params( - purchaseTransaction.first().purchaseToken, - purchaseTransaction.first().productId.first(), - customer, - Chargebee.channel - ) - CBRestorePurchaseManager.syncPurchaseWithChargebee(purchaseTransaction) - CoroutineScope(Dispatchers.IO).launch { - Mockito.`when`(params.let { ReceiptResource().validateReceipt(it) }).thenReturn( - ChargebeeResult.Error( - error - ) - ) - Mockito.verify(ReceiptResource(), Mockito.times(1)).validateReceipt(params) - Mockito.verify(CBReceiptRequestBody("receipt", "", null, ""), Mockito.times(1)) - .toCBReceiptReqBody() - } - } - private fun getTransaction(isTestingSuccess: Boolean): ArrayList { storeTransactions.clear() val result = if (isTestingSuccess)