-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Omnisub 5499 - Upgrade/downgrade subscription #98
base: master
Are you sure you want to change the base?
Changes from 5 commits
79acc6c
08d0006
942e453
94f2f6f
203dc88
e8cd2e6
c6b1758
83b7702
e66769f
08616b0
484d8b9
27c85e6
7b706ca
56593af
62c3248
803326f
159809a
7e0d6da
ecb90c4
90f39e3
2c98f6a
86676e1
f8b63b0
ba2d1a7
e9c5422
418c231
fb2ae76
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||
xmlns:app="http://schemas.android.com/apk/res-auto" | ||
xmlns:tools="http://schemas.android.com/tools" | ||
android:layout_width="300dp" | ||
android:layout_height="300dp" | ||
tools:context=".MainActivity"> | ||
|
||
<com.google.android.material.textfield.TextInputLayout | ||
android:id="@+id/planIdInputLayout" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:layout_marginTop="96dp" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toBottomOf="@+id/textView"> | ||
|
||
<EditText | ||
android:id="@+id/productIdInput" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:ems="13" | ||
android:inputType="textPersonName" /> | ||
</com.google.android.material.textfield.TextInputLayout> | ||
|
||
<com.google.android.material.textfield.TextInputLayout | ||
android:id="@+id/oldPlanIdInputLayout" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:layout_marginTop="20dp" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintHorizontal_bias="0.497" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toBottomOf="@+id/textView"> | ||
|
||
<EditText | ||
android:id="@+id/oldProductIdInput" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:ems="13" | ||
android:inputType="textPersonName" /> | ||
</com.google.android.material.textfield.TextInputLayout> | ||
|
||
<TextView | ||
android:id="@+id/textView" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:layout_marginTop="20dp" | ||
android:text="Chargebee" | ||
android:textSize="24sp" | ||
android:textStyle="bold" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toTopOf="parent" /> | ||
|
||
<Button | ||
android:id="@+id/btn_ok" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:layout_marginTop="34dp" | ||
android:text="Submit" | ||
app:layout_constraintEnd_toEndOf="parent" | ||
app:layout_constraintStart_toStartOf="parent" | ||
app:layout_constraintTop_toBottomOf="@+id/planIdInputLayout" /> | ||
|
||
</androidx.constraintlayout.widget.ConstraintLayout> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ 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.ChangeProductParams | ||
import com.chargebee.android.models.PricingPhase | ||
import com.chargebee.android.models.PurchaseProductParams | ||
import com.chargebee.android.models.PurchaseTransaction | ||
|
@@ -29,6 +30,7 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { | |
private var purchaseCallBack: CBCallback.PurchaseCallback<String>? = null | ||
private val TAG = javaClass.simpleName | ||
private lateinit var purchaseProductParams: PurchaseProductParams | ||
private lateinit var changeProductParams: ChangeProductParams | ||
private lateinit var restorePurchaseCallBack: CBCallback.RestorePurchaseCallback | ||
private var oneTimePurchaseCallback: CBCallback.OneTimePurchaseCallback? = null | ||
|
||
|
@@ -253,6 +255,102 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { | |
|
||
} | ||
|
||
internal fun changeProduct( | ||
changeProductParams: ChangeProductParams, | ||
purchaseCallBack: CBCallback.PurchaseCallback<String> | ||
) { | ||
this.purchaseCallBack = purchaseCallBack | ||
onConnected({ status -> | ||
if (status) { | ||
changeProduct(changeProductParams) | ||
} else | ||
purchaseCallBack.onError( | ||
connectionError | ||
) | ||
}, { error -> | ||
purchaseCallBack.onError(error) | ||
}) | ||
|
||
} | ||
|
||
private fun changeProduct(changeProductParams: ChangeProductParams) { | ||
this.changeProductParams = changeProductParams | ||
this.purchaseProductParams = changeProductParams.purchaseProductParams | ||
val offerToken = changeProductParams.purchaseProductParams.offerToken | ||
val oldProductId = changeProductParams.oldProductId | ||
val prorationMode = changeProductParams.prorationMode ?: BillingFlowParams.ProrationMode.IMMEDIATE_WITH_TIME_PRORATION | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's start with support for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
||
val queryProductDetails = arrayListOf(QueryProductDetailsParams.Product.newBuilder() | ||
.setProductId(this.purchaseProductParams.product.id) | ||
.setProductType(this.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()) | ||
|
||
queryAllSubsPurchaseHistory(ProductType.SUBS.value) { subscriptionHistory -> | ||
val oldPurchaseToken: String = getOldPurchaseToken(subscriptionHistory, oldProductId) | ||
|
||
val billingFlowParams = | ||
BillingFlowParams.newBuilder() | ||
.setProductDetailsParamsList(productDetailsParamsList) | ||
.setSubscriptionUpdateParams( | ||
BillingFlowParams.SubscriptionUpdateParams.newBuilder() | ||
.setOldPurchaseToken(oldPurchaseToken) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would be the response if the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's loading infinitely. Handled it now with an error message. |
||
.setReplaceProrationMode(prorationMode).build() | ||
).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 == this.purchaseProductParams.product.type) { | ||
purchaseCallBack?.onError( | ||
billingError | ||
) | ||
} else { | ||
oneTimePurchaseCallback?.onError( | ||
billingError | ||
) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to handle for oneTimePurchaseCallback? Since OTP cannot be changed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missed it!!... Removed now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this removed yet? |
||
} | ||
} | ||
} else { | ||
Log.e(TAG, "Failed to fetch product :" + billingResult.responseCode) | ||
if (ProductType.SUBS == this.purchaseProductParams.product.type) { | ||
purchaseCallBack?.onError(throwCBException(billingResult)) | ||
} else { | ||
oneTimePurchaseCallback?.onError(throwCBException(billingResult)) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to handle for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missed it!!... Removed now. |
||
|
||
} | ||
} | ||
|
||
} | ||
|
||
private fun getOldPurchaseToken(subscriptionHistory: List<PurchaseTransaction>?, oldProductId: String): String { | ||
val purchaseTransactionHistory = mutableListOf<PurchaseTransaction>() | ||
purchaseTransactionHistory.addAll(subscriptionHistory ?: emptyList()) | ||
val prevProduct: PurchaseTransaction? = purchaseTransactionHistory.find { it.productId.first() == oldProductId } | ||
return prevProduct?.purchaseToken ?: "" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: return an empty optional instead of empty string There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
} | ||
|
||
/** | ||
* This method will provide all the purchases associated with the current account based on the [includeInActivePurchases] flag set. | ||
* And the associated purchases will be synced with Chargebee. | ||
|
@@ -333,9 +431,21 @@ class BillingClientManager(context: Context) : PurchasesUpdatedListener { | |
|
||
else -> { | ||
if (purchaseProductParams.product.type == ProductType.SUBS) | ||
purchaseCallBack?.onError( | ||
throwCBException(billingResult) | ||
) | ||
if (this.changeProductParams.oldProductId.isNotEmpty() && billingResult.responseCode == ERROR) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Where do we reset the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
purchaseCallBack?.onError( | ||
CBException( | ||
ErrorDetail( | ||
message = GPErrorCode.InvalidProductIdError.errorMsg, | ||
httpStatusCode = billingResult.responseCode | ||
) | ||
) | ||
) | ||
} | ||
else { | ||
purchaseCallBack?.onError( | ||
throwCBException(billingResult) | ||
) | ||
} | ||
else | ||
oneTimePurchaseCallback?.onError( | ||
throwCBException(billingResult) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to set this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes its used in acknowledgePurchase method