Skip to content

Commit

Permalink
Merge pull request #67 from cb-amutha/feature/restore_purchase
Browse files Browse the repository at this point in the history
[Feature] Restore purchase support
  • Loading branch information
cb-amutha authored May 12, 2023
2 parents 0e14cba + 8f930f2 commit 78c7a52
Show file tree
Hide file tree
Showing 16 changed files with 1,083 additions and 356 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,42 @@ CBPurchase.purchaseProduct(product=CBProduct, customer=CBCustomer, object : CBCa
```
The above function will handle the purchase against Google Play Store and send the IAP token for server-side token verification to your Chargebee account. Use the Subscription ID returned by the above function, to check for Subscription status on Chargebee and confirm the access - granted or denied.

### Restore Purchase

The `restorePurchases()` function helps to recover your app user's previous purchases without making them pay again. Sometimes, your app user may want to restore their previous purchases after switching to a new device or reinstalling your app. You can use the `restorePurchases()` function to allow your app user to easily restore their previous purchases.
To retrieve **inactive** purchases along with the **active** purchases for your app user, you can call the `restorePurchases()` function with the `includeInActiveProducts` parameter set to `true`. If you only want to restore active subscriptions, set the parameter to `false`. Here is an example of how to use the `restorePurchases()` function in your code with the `includeInActiveProducts` parameter set to `true`.
```kotlin
CBPurchase.restorePurchases(context = current activity context, includeInActivePurchases = false, object : CBCallback.RestorePurchaseCallback{
override fun onSuccess(result: List<CBRestoreSubscription>) {
result.forEach {
Log.i(javaClass.simpleName, "Successfully restored purchases")
}
}
override fun onError(error: CBException) {
Log.e(TAG, "Error: ${error.message}")
}
})
```
##### Return Subscriptions Object
The `restorePurchases()` function returns an array of subscription objects and each object holds three attributes `subscription_id`, `plan_id`, and `store_status`. The value of `store_status` can be used to verify the subscription status such as `Active`, `InTrial`, `Cancelled` and `Paused`.
##### Error Handling
In the event of any failures while finding associated subscriptions for the restored items, The SDK will return an error, as mentioned in the following table.
These are the possible error codes and their descriptions:
| Error Code | Description |
|-----------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
| `BillingErrorCode.SERVICE_TIMEOUT` | The request has reached the maximum timeout before Google Play responds. |
| `BillingErrorCode.FEATURE_NOT_SUPPORTED` | The requested feature is not supported by the Play Store on the current device. |
| `BillingErrorCode.SERVICE_UNAVAILABLE` | The service is currently unavailable. |
| `BillingErrorCode.DEVELOPER_ERROR` | Error resulting from incorrect usage of the API. |
| `BillingErrorCode.ERROR` | Fatal error during the API action. |
| `BillingErrorCode.SERVICE_DISCONNECTED` | The app is not connected to the Play Store service via the Google Play Billing Library. |
| `BillingErrorCode.UNKNOWN` | Unknown error occurred. |
### Get Subscription Status for Existing Subscribers
The following are methods for checking the subscription status of a subscriber who already purchased the product.
Expand Down
58 changes: 45 additions & 13 deletions app/src/main/java/com/chargebee/example/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ 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.exceptions.CBProductIDResult
import com.chargebee.android.models.CBProduct
import com.chargebee.example.adapter.ListItemsAdapter
import com.chargebee.example.addon.AddonActivity
Expand All @@ -38,12 +38,12 @@ import kotlinx.coroutines.launch

class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener {
private var mItemsRecyclerView: RecyclerView? = null
private var list = arrayListOf<String>()
private var list = arrayListOf<String>()
var listItemsAdapter: ListItemsAdapter? = null
var featureList = mutableListOf<CBMenu>()
var mContext: Context? = null
private val gson = Gson()
private var mBillingViewModel : BillingViewModel? = null
private var mBillingViewModel: BillingViewModel? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down Expand Up @@ -75,7 +75,7 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener {
}
}

private fun setListAdapter(){
private fun setListAdapter() {
featureList = CBMenu.values().toMutableList()
listItemsAdapter = ListItemsAdapter(featureList, this)
val layoutManager: RecyclerView.LayoutManager = LinearLayoutManager(applicationContext)
Expand All @@ -85,7 +85,7 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener {
}

override fun onItemClick(view: View?, position: Int) {
when(CBMenu.valueOf(featureList.get(position).toString()).value){
when (CBMenu.valueOf(featureList.get(position).toString()).value) {
CBMenu.Configure.value -> {
if (view != null) {
onClickConfigure(view)
Expand Down Expand Up @@ -132,8 +132,11 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener {
CBMenu.GetEntitlements.value -> {
getSubscriptionId()
}
else ->{
Log.i(javaClass.simpleName, " Not implemented" )
CBMenu.RestorePurchase.value -> {
restorePurchases()
}
else -> {
Log.i(javaClass.simpleName, " Not implemented")
}
}
}
Expand All @@ -148,9 +151,9 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener {
val builder = AlertDialog.Builder(this)
val inflater = layoutInflater
val dialogLayout = inflater.inflate(R.layout.activity_configure, null)
val siteNameEditText = dialogLayout.findViewById<EditText>(R.id.etv_siteName)
val apiKeyEditText = dialogLayout.findViewById<EditText>(R.id.etv_apikey)
val sdkKeyEditText = dialogLayout.findViewById<EditText>(R.id.etv_sdkkey)
val siteNameEditText = dialogLayout.findViewById<EditText>(R.id.etv_siteName)
val apiKeyEditText = dialogLayout.findViewById<EditText>(R.id.etv_apikey)
val sdkKeyEditText = dialogLayout.findViewById<EditText>(R.id.etv_sdkkey)
builder.setView(dialogLayout)
builder.setPositiveButton("Initialize") { _, i ->

Expand Down Expand Up @@ -182,27 +185,56 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener {
}
dialog.show()
}
private fun getProductIdList(productIdList: ArrayList<String>){

private fun getProductIdList(productIdList: ArrayList<String>) {
CBPurchase.retrieveProducts(
this,
productIdList,
object : CBCallback.ListProductsCallback<ArrayList<CBProduct>> {
override fun onSuccess(productIDs: ArrayList<CBProduct>) {
CoroutineScope(Dispatchers.Main).launch {
if (productIDs.size > 0) {
launchProductDetailsScreen(gson.toJson(productIDs))
launchProductDetailsScreen(gson.toJson(productIDs))
} else {
alertSuccess("Items not available to buy")
}
}
}

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

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)
Expand All @@ -215,7 +247,7 @@ class MainActivity : BaseActivity(), ListItemsAdapter.ItemClickListener {
javaClass.simpleName,
" Item clicked :" + list[which] + " position :" + which
)
val productIdList = ArrayList<String>()
val productIdList = ArrayList<String>()
productIdList.add(list[which].trim())
getProductIdList(productIdList)
}
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/com/chargebee/example/util/CBMenu.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ enum class CBMenu(val value: String) {
GetProducts("Get Products"),
SubsStatus("Get Subscription Status"),
SubsList("Get Subscriptions List"),
GetEntitlements("Get Entitlements")
GetEntitlements("Get Entitlements"),
RestorePurchase("Restore Purchase")
}

Loading

0 comments on commit 78c7a52

Please sign in to comment.