---
id: "subscription-guidance/managing-subscriptions"
title: "Upgrades, Downgrades, & Management"
description: "Some parts of a customer's subscription can be managed directly through RevenueCat, other parts can only be managed by the customer directly in the respective stores (Apple, Google, Stripe, and Amazon). Learn how to upgrade/downgrade, cancel, and refund subscriptions here!"
permalink: "/docs/subscription-guidance/managing-subscriptions"
slug: "managing-subscriptions"
version: "current"
original_source: "docs/subscription-guidance/managing-subscriptions.mdx"
---

> **AI agents:** This is the Markdown version of a RevenueCat documentation page. For the complete documentation index, see [llms.txt](https://www.revenuecat.com/docs/llms.txt).

Some parts of a customer's subscription can be managed directly through RevenueCat, other parts can only be managed by the customer directly in the respective stores (Apple, Google, Stripe, and Amazon). Learn how to upgrade/downgrade, cancel, and refund subscriptions here!

For information about Stripe, you can read more about working with web payments [here](https://www.revenuecat.com/docs/web/integrations/stripe#working-with-web-payments).

## Upgrade or Downgrade a Subscription

:::info\[Developers cannot change a customer's subscription directly]
The app stores do not allow developers (you) to upgrade or downgrade a subscription on behalf of a customer. Only a customer can change their subscription.
:::

### App Store

There are no code changes required to support upgrades, downgrades, and crossgrades for iOS subscriptions in your app. A customer can choose to upgrade, downgrade, or crossgrade between subscriptions as often as they like.

According to [Apple](https://developer.apple.com/app-store/subscriptions#ranking), when a customer changes their subscription level, access to the new product can vary depending on the change:

> **Upgrade**. A user purchases a subscription that offers a higher level of service than their current subscription. They are immediately upgraded and receive a refund of the prorated amount of their original subscription. If you’d like users to immediately access more content or features, rank the subscription higher to make it an upgrade.
>
> **Downgrade**. A user selects a subscription that offers a lower level of service than their current subscription. The subscription continues until the next renewal date, then is renewed at the lower level and price.
>
> **Crossgrade**. A user switches to a new subscription of the equivalent level. If the subscriptions are the same duration, the new subscription begins immediately. If the durations are different, the new subscription goes into effect at the next renewal date.

You can refer to this [blog post](https://www.revenuecat.com/blog/ios-subscription-groups-explained) for more information on how to set up subscription groups in App Store Connect.

:::info\[Upgrades during introductory periods]
When a customer upgrades products during an introductory period (including a free trial), Apple does not cancel the introductory offer but instead keeps the introductory offer active in addition to the upgraded product. So in these cases you can expect two products in the same Subscription Group to be active simultaneously.
:::

### Google Play

Google Play subscription changes require a product change purchase with the customer's existing subscription and a replacement mode.

If you're using RevenueCat Paywalls, configure the **Play Store only: Replacement mode** setting in the Paywall Editor. On supported SDK versions, RevenueCat Paywalls will automatically apply the configured replacement mode when an existing Play Store subscriber purchases a different product from the paywall. Learn more in [Product changes (Google Play)](https://www.revenuecat.com/docs/tools/paywalls/creating-paywalls/product-changes).

If you're building a custom paywall or purchase flow, you will need to provide the old product ID and an optional replacement mode in your purchase call. Replacement mode will default to `WITHOUT_PRORATION`.

**Kotlin**

```kotlin
Purchases.sharedInstance.purchase(
    PurchaseParams.Builder(requireActivity(), pkg)
        .oldProductId("oldProductId:oldBasePlanId")
        .replacementMode(StoreReplacementMode.WITHOUT_PRORATION)
        .build(),
    object : PurchaseCallback {
        override fun onCompleted(storeTransaction: StoreTransaction, customerInfo: CustomerInfo) {
            val entitlement = customerInfo.entitlements["my_entitlement_identifier"]
            if (entitlement != null && entitlement.isActive) {
                // Unlock that great "pro" content
            }
        }

        override fun onError(purchasesError: PurchasesError, b: Boolean) {
            // No purchase
        }
    }
)
```

**Java**

```java
Purchases.getSharedInstance().purchase(
	new PurchaseParams.Builder(activity, pkg)
		.oldProductId("oldProductId:oldBasePlanId")
		.replacementMode(StoreReplacementMode.WITHOUT_PRORATION)
		.build(),
	new PurchaseCallback() {
		@Override
		public void onCompleted(@NonNull StoreTransaction storeTransaction, @NonNull CustomerInfo customerInfo) {
			if (customerInfo.getEntitlements().get("my_entitlement_identifier").isActive()) {
				// Unlock that great "pro" content
			}
		}

		@Override
		public void onError(@NonNull PurchasesError purchasesError, boolean b) {
			// No purchase
		}
	}
);
```

```jsx
const productChangeInfo = {
  oldProductIdentifier: "oldProductId",
  replacementMode: Purchases.STORE_REPLACEMENT_MODE.WITHOUT_PRORATION,
};

const { customerInfo } = await Purchases.purchaseStoreProduct(
  newProduct,
  productChangeInfo,
);

if (customerInfo.entitlements.active["my_entitlement_identifier"]) {
  // Unlock that great "pro" content
}
```

```dart
final productChangeInfo = StoreProductChangeInfo(
  "oldProductId",
  replacementMode: StoreReplacementMode.withoutProration,
);

final purchaseResult = await Purchases.purchase(
  PurchaseParams.storeProduct(
    newProduct,
    productChangeInfo: productChangeInfo,
  ),
);

if (purchaseResult.customerInfo.entitlements.all["my_entitlement_identifier"]?.isActive == true) {
  // Unlock that great "pro" content
}
```

```cpp
var purchases = GetComponent<Purchases>();
purchases.PurchasePackage(newPackage, (transaction, customerInfo, userCancelled, error) =>
{
    if (customerInfo.Entitlements.Active.ContainsKey("my_entitlement_identifier"))
    {
        // Unlock that great "pro" content
    }
},
oldSku: "oldProductId",
prorationMode: Purchases.ProrationMode.ImmediateWithoutProration);
```

```jsx
const { customerInfo } = await Purchases.purchaseStoreProduct({
  product: newProduct,
  storeProductChangeInfo: {
    oldProductIdentifier: "oldProductId",
    replacementMode: STORE_REPLACEMENT_MODE.WITHOUT_PRORATION,
  },
});

if (customerInfo.entitlements.active["my_entitlement_identifier"]) {
  // Unlock that great "pro" content
}
```

Google documentation provides [examples of each replacement mode](https://developer.android.com/google/play/billing/subscriptions#replacement-modes), behavior when [upgrading with free trial or intro price offers](https://developer.android.com/google/play/billing/subscriptions#upgrade-free-trial), and [recommendations](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) for which replacement mode to use in different scenarios.

| Mode                      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **WITHOUT\_PRORATION**     | Old subscription is cancelled, and new subscription takes effect immediately. User is charged for the full price of the new subscription on the old subscription's expiration date. This is the default behavior.                                                                                                                                                                                                                                 |
| **WITH\_TIME\_PRORATION**   | Old subscription is cancelled, and new subscription takes effect immediately. Any time remaining on the old subscription is used to push out the first payment date for the new subscription. User is charged the full price of new subscription once that prorated time has passed. The purchase will fail if this mode is used when switching between `SubscriptionOption`s of the same `StoreProduct`.                                         |
| **CHARGE\_FULL\_PRICE**     | Replacement takes effect immediately, and the user is charged full price of new plan and is given a full billing cycle of subscription, plus remaining prorated time from the old plan.                                                                                                                                                                                                                                                           |
| **CHARGE\_PRORATED\_PRICE** | Replacement takes effect immediately, and the billing cycle remains the same. The price difference for the remaining period is then charged to the user. Note: This option is available only for a subscription upgrade, where the price per unit of time increases. Google recommends this mode for upgrading to a more expensive tier, and for upgrading while in a free trial which will end access to the free trial.                         |
| **DEFERRED**              | Replacement takes effect when the old plan expires, and the new price will be charged at the same time. Google recommends this mode for downgrading to a less expensive tier, and for changing recurring period on the same tier (from monthly to annual). **Important**: [Google Server Notifications](https://www.revenuecat.com/docs/platform-resources/server-notifications/google-server-notifications) are required to be configured for `DEFERRED` mode to work properly. |

### Amazon Appstore

Amazon does not support changing products. Customers will need to cancel their existing subscription and re-subscribe to a different product.

### Roku

- **Upgrades**: Upgrading products will cancel the previous subscription and start the new one immediately
- **Downgrades**: Downgrades schedule the subscription change for when the current billing period ends, then the new subscription product will take effect

Please note when your customer cancels a pending downgrade transaction through the Roku dashboard, this will not only cancel the downgrade, but will also turn off auto-renew for the existing subscription product.

### RevenueCat Billing

You can allow customers to upgrade or downgrade their subscription from the web Customer Portal. To enable this, you first need to create upgrade or downgrade paths between your RevenueCat Billing products.

- **Upgrades** happen immediately, and any unused time on the existing subscription cycle is credited through a partial refund.
- **Downgrades** are scheduled for the end of the current cycle, and are not prorated.

See [how to configure RevenueCat Billing subscription changes](https://www.revenuecat.com/docs/web/web-billing/product-setup#configure-subscription-changes).

### Considerations

#### [PRODUCT\_CHANGE](https://www.revenuecat.com/docs/dashboard-and-metrics/customer-profile#event-types) events and webhooks

- The `expiration_at_ms` will always be for the product the customer is changing from (old product).
- The `PRODUCT_CHANGE` webhook should be considered informative, and does not mean that the product change has gone into effect. When the product change goes into effect you will receive a `RENEWAL` event on Apple and Stripe or a `INITIAL_PURCHASE` event on Google Play.

## Prorations and Revenue Metrics

### App Store

For Apple transactions, prorated revenue **will not** be shown in the [Customer History](https://www.revenuecat.com/docs/dashboard-and-metrics/customer-profile) page. However, prorated revenue **will** be calculated for your chart and overview data.

### Google Play

For Google transactions, prorated revenue will be shown in the [Customer History](https://www.revenuecat.com/docs/dashboard-and-metrics/customer-profile) page and will be calculated for your chart and overview data.

## Cancelling Subscriptions

Cancelling (or unsubscribing) from subscriptions is handled differently on each platform. Cancellations are automatically detected by RevenueCat within a few hours of occurring. This detection time can be increased to near real-time by enabling [Platform Server Notifications](https://www.revenuecat.com/docs/platform-resources/server-notifications).

| Store       | Behavior                                                                                                                                                                                                                                                                                                                                                                                            |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Apple       | Apple does not allow developers to manage subscriptions on behalf of users. Your customers have to manually opt-out of renewal. The [Apple subscription terms](https://support.apple.com/en-us/HT202039) require users to cancel subscriptions at least 24 hours before the next renewal.                                                                                                           |
| Google Play | Google allows developers to cancel auto-renewing subscriptions on behalf of customers via Google Play Console or Google's API. For prepaid subscriptions, neither the customer nor the developer can cancel the subscription as it already has an expiration date. If you're looking to refund and revoke a subscription via RevenueCat's dashboard, see [Refunds](https://www.revenuecat.com/docs/subscription-guidance/refunds). |
| Stripe      | Subscriptions can be cancelled on behalf of customers via the Stripe dashboard or REST API. Refer to Stripe documentation for more info.                                                                                                                                                                                                                                                            |
| Amazon      | Amazon does not allow developers to cancel subscriptions on behalf of users. Once a subscription is purchased for a period, it is valid through that period and cannot be cancelled.                                                                                                                                                                                                                |

### Using the `managementURL` to Help Customers Cancel a Subscription

Google requires developers to allow customers to cancel a subscription within apps. You can do this by displaying a link in your app that takes the user directly to the store's subscription management screen where they can immediately cancel their subscription. RevenueCat helps you do this by providing a `managementURL` property on the CustomerInfo object in the SDK and in our [REST API](https://docs.revenuecat.com/reference/subscribers#the-subscriber-object).

:::info
The `managementURL` is a great way to allow customers to check the status of and manage their subscriptions. RevenueCat automatically provides your app with the correct `managementURL` based on the platform of the customer's device and the store where they purchased their subscription. Supported stores include the App Store, Google Play, Amazon Appstore, RevenueCat Web Billing Customer Portal, and Paddle Customer Portal.
:::

:::info\[Platform support]
`managementURL` is supported for App Store, Google Play, Amazon Appstore, RevenueCat Web Billing, and Paddle subscriptions.

For Stripe Billing subscriptions, RevenueCat returns your configured [Stripe Customer Portal URL](https://www.revenuecat.com/docs/web/integrations/stripe#subscription-management) (or a custom URL) in `managementURL`. If no Stripe Customer Portal URL is configured, `managementURL` will be null.
:::

**Swift**

```swift
Purchases.shared.getCustomerInfo { (customerInfo, error) in
    let managementURL = customerInfo.managementURL
    // display the managementURL in your app
}
```

**Kotlin**

```kotlin
Purchases.sharedInstance.getCustomerInfoWith(
  onError = { error -> /* Optional error handling */ },
  onSuccess = { customerInfo ->
    val managementURL = customerInfo.managementURL
    // display the managementURL in your app
  }
)
```

:::warning\[Deleting a User]
Deleting a user from RevenueCat **WILL NOT** cancel their subscription. The user can still trigger the [Restore Purchases](https://www.revenuecat.com/docs/getting-started/restoring-purchases) method to re-sync their transactions with RevenueCat servers.
:::
