Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 10 additions & 29 deletions app/src/main/java/com/chargebee/example/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -133,7 +142,7 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener {
getSubscriptionId()
}
CBMenu.RestorePurchase.value -> {
restorePurchases()
mBillingViewModel?.restorePurchases(this)
}
else -> {
Log.i(javaClass.simpleName, " Not implemented")
Expand Down Expand Up @@ -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<CBRestoreSubscription>) {
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<String>) {
val builder = AlertDialog.Builder(this)
builder.setTitle("Chargebee Product IDs")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class BillingViewModel : ViewModel() {
var entitlementsResult: MutableLiveData<String?> = MutableLiveData()
private var subscriptionId: String = ""
private lateinit var sharedPreference : SharedPreferences
var restorePurchaseResult: MutableLiveData<List<CBRestoreSubscription?>> = 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.
Expand Down Expand Up @@ -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<CBRestoreSubscription>) {
result.forEach {
Log.i(javaClass.simpleName, "status : ${it.storeStatus}")
}
restorePurchaseResult.postValue(result)
}

override fun onError(error: CBException) {
cbException.postValue(error)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -309,10 +310,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener {
queryPurchaseHistory { purchaseHistoryList ->
val storeTransactions = arrayListOf<PurchaseTransaction>()
storeTransactions.addAll(purchaseHistoryList)
CBRestorePurchaseManager.fetchStoreSubscriptionStatus(
storeTransactions,
restorePurchaseCallBack
)
fetchStoreSubscriptionStatus(storeTransactions)
}
} else {
restorePurchaseCallBack.onError(
Expand All @@ -321,6 +319,59 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener {
}
}

internal fun fetchStoreSubscriptionStatus(storeTransactions: ArrayList<PurchaseTransaction>){
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<PurchaseTransaction>) {
storeTransactions.forEach { productIdList ->
retrieveProducts(
CBPurchase.ProductType.SUBS.value,
ArrayList(productIdList.productId),
object : CBCallback.ListProductsCallback<ArrayList<CBProduct>> {
override fun onSuccess(productIDs: ArrayList<CBProduct>) {
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}"
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How could this be conveyed to the app?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are just doing sync purchase. this not required to be notified to user.

}
}
}

override fun onError(error: CBException) {
Log.e(javaClass.simpleName, "Error: ${error.message}")
}
})
}
}

private fun queryPurchaseHistory(
storeTransactions: (List<PurchaseTransaction>) -> Unit
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ object CBPurchase {
val productIdList = arrayListOf<String>()
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"),
Expand Down Expand Up @@ -168,7 +171,8 @@ object CBPurchase {
completion: (ChargebeeResult<Any>) -> 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(
Expand All @@ -184,14 +188,16 @@ object CBPurchase {
internal fun validateReceipt(
purchaseToken: String,
productId: String,
offerDetail: OfferDetail?,
completion: (ChargebeeResult<Any>) -> Unit
) {
val logger = CBLogger(name = "buy", action = "process_purchase_command")
val params = Params(
purchaseToken,
productId,
customer,
Chargebee.channel
Chargebee.channel,
offerDetail
)
ResultHandler.safeExecuter(
{ ReceiptResource().validateReceipt(params) },
Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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");
}
Original file line number Diff line number Diff line change
@@ -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
)
}
}
Expand All @@ -25,7 +29,7 @@ internal class CBReceiptRequestBody( val receipt: String,
}

fun toCBReceiptReqCustomerBody(): Map<String, String?> {
return mapOf(
val queryMap = mutableMapOf(
"receipt" to this.receipt,
"product[id]" to this.productId,
"customer[id]" to this.customer?.id,
Expand All @@ -34,21 +38,33 @@ internal class CBReceiptRequestBody( val receipt: String,
"customer[email]" to this.customer?.email,
"channel" to this.channel
)
return getOfferParams(queryMap)
}
fun toMap(): Map<String, String> {
return mapOf(
fun toMap(): Map<String, String?> {
val queryMap = mutableMapOf<String, String?>(
"receipt" to this.receipt,
"product[id]" to this.productId,
"channel" to this.channel
)
return getOfferParams(queryMap)
}

private fun getOfferParams(queryMap: MutableMap<String, String?>) : Map<String, String?>{
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
}
}

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?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import com.chargebee.android.responseFromServer
internal class ReceiptResource : BaseResource(baseUrl = Chargebee.baseUrl){

internal suspend fun validateReceipt(params: Params): ChargebeeResult<Any> {
var dataMap = mapOf<String, String?>()
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()
Expand Down
Loading