-
Notifications
You must be signed in to change notification settings - Fork 4
Subscription Upgrades
Muhammad Hammad edited this page Nov 29, 2025
·
1 revision
AdManageKit v2.9.0 provides full support for subscription tier changes with configurable proration modes.
Upgrades use CHARGE_PRORATED_PRICE mode - user pays the price difference immediately.
// User is on "basic_monthly", upgrade to "premium_monthly"
AppPurchase.getInstance().upgradeSubscription(activity, "premium_monthly")Downgrades use DEFERRED mode - change takes effect at the next renewal date.
// User is on "premium_monthly", downgrade to "basic_monthly"
AppPurchase.getInstance().downgradeSubscription(activity, "basic_monthly")For precise control over which subscription to replace and how:
AppPurchase.getInstance().changeSubscription(
activity,
"premium_monthly", // Current subscription ID
"premium_yearly", // New subscription ID
SubscriptionReplacementMode.CHARGE_PRORATED_PRICE // Proration mode
)| Mode | Effect | Best For |
|---|---|---|
CHARGE_PRORATED_PRICE |
User pays price difference immediately | Upgrades |
DEFERRED |
Change at next renewal, no immediate charge | Downgrades |
WITH_TIME_PRORATION |
Immediate change, remaining value credited | Either |
CHARGE_FULL_PRICE |
Immediate change, full price charged | Revenue maximization |
WITHOUT_PRORATION |
Immediate change, billing date unchanged | Same-tier changes |
// User pays difference between old and new subscription
// Example: Monthly $5 → Yearly $50
// User pays: $50 - ($5 * remaining_days/30)// No immediate change - user keeps current subscription until renewal
// At renewal, switches to new (lower) subscription
// Good for user experience - they get what they paid for// Immediate switch with time-based credit
// Remaining value of old subscription credited toward new
// Useful for same-price tier changes// Immediate switch, full price charged
// Old subscription remaining value lost
// Maximum immediate revenue// Immediate switch, no price adjustment
// Billing cycle remains the same
// Use for same-price tier switchesclass SubscriptionTiersActivity : AppCompatActivity() {
private val tiers = listOf(
Tier("basic_monthly", "Basic", "$4.99/mo"),
Tier("premium_monthly", "Premium", "$9.99/mo"),
Tier("premium_yearly", "Premium Annual", "$99.99/yr")
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_tiers)
val currentSub = AppPurchase.getInstance().getActiveSubscriptions().firstOrNull()
val currentTierId = currentSub?.getFirstProductId()
tiers.forEach { tier ->
val button = createTierButton(tier)
when {
tier.id == currentTierId -> {
button.text = "Current Plan"
button.isEnabled = false
}
isUpgrade(currentTierId, tier.id) -> {
button.text = "Upgrade to ${tier.name}"
button.setOnClickListener { upgradeTo(tier.id) }
}
isDowngrade(currentTierId, tier.id) -> {
button.text = "Downgrade to ${tier.name}"
button.setOnClickListener { downgradeTo(tier.id) }
}
else -> {
button.text = "Subscribe to ${tier.name}"
button.setOnClickListener { subscribeTo(tier.id) }
}
}
}
}
private fun upgradeTo(tierId: String) {
AppPurchase.getInstance().upgradeSubscription(this, tierId)
}
private fun downgradeTo(tierId: String) {
AppPurchase.getInstance().downgradeSubscription(this, tierId)
}
private fun subscribeTo(tierId: String) {
AppPurchase.getInstance().subscribe(this, tierId)
}
private fun isUpgrade(current: String?, new: String): Boolean {
val currentIndex = tiers.indexOfFirst { it.id == current }
val newIndex = tiers.indexOfFirst { it.id == new }
return currentIndex != -1 && newIndex > currentIndex
}
private fun isDowngrade(current: String?, new: String): Boolean {
val currentIndex = tiers.indexOfFirst { it.id == current }
val newIndex = tiers.indexOfFirst { it.id == new }
return currentIndex != -1 && newIndex < currentIndex
}
}The upgrade/downgrade uses the same purchase flow:
AppPurchase.getInstance().setPurchaseListener(object : PurchaseListener {
override fun onProductPurchased(orderId: String?, originalJson: String?) {
// Subscription changed successfully
Toast.makeText(this, "Subscription updated!", Toast.LENGTH_SHORT).show()
updateUI()
}
override fun displayErrorMessage(errorMessage: String?) {
Toast.makeText(this, errorMessage, Toast.LENGTH_SHORT).show()
}
override fun onUserCancelBilling() {
// User cancelled - no action needed
}
})For direct access to the purchase token:
val subscription = AppPurchase.getInstance().getSubscription("premium_monthly")
if (subscription != null) {
AppPurchase.getInstance().updateSubscription(
activity,
"premium_yearly", // New subscription ID
subscription.purchaseToken, // Current purchase token
SubscriptionReplacementMode.WITH_TIME_PRORATION
)
}// User wants annual billing for same features
AppPurchase.getInstance().changeSubscription(
activity,
"premium_monthly",
"premium_yearly",
SubscriptionReplacementMode.CHARGE_PRORATED_PRICE // Pay difference for remaining month
)// User wants monthly billing for same features
AppPurchase.getInstance().changeSubscription(
activity,
"premium_yearly",
"premium_monthly",
SubscriptionReplacementMode.DEFERRED // Change at renewal
)AppPurchase.getInstance().changeSubscription(
activity,
"basic_monthly",
"premium_monthly",
SubscriptionReplacementMode.CHARGE_PRORATED_PRICE
)AppPurchase.getInstance().changeSubscription(
activity,
"premium_monthly",
"basic_monthly",
SubscriptionReplacementMode.DEFERRED
)val result = AppPurchase.getInstance().changeSubscription(
activity,
"current_sub",
"new_sub",
SubscriptionReplacementMode.CHARGE_PRORATED_PRICE
)
when {
result == "OK" -> {
// Billing flow started successfully
}
result.contains("not found") -> {
// Subscription ID invalid or not loaded
Log.e("Billing", "Invalid subscription ID")
}
result.contains("No active subscription") -> {
// User doesn't have the source subscription
// Fall back to regular subscribe
AppPurchase.getInstance().subscribe(activity, "new_sub")
}
}- Use DEFERRED for downgrades - Users appreciate getting what they paid for
- Use CHARGE_PRORATED_PRICE for upgrades - Immediate access, fair pricing
- Show clear pricing - Display what user will pay before confirming
- Handle multiple subscriptions - User might have multiple active subscriptions
- Test all scenarios - Use Google Play Console's test tracks
AdManageKit v3.3.4 | GitHub | API Docs | Report Issue | Buy me a coffee