VoidhashDocs
DevelopersHelp centerGuidesAPI reference
Dashboard
Guides
Launch
Go-live checklist
Make your first App Store purchase
Make your first Google Play purchase
Troubleshoot
Troubleshooting
Recover from incidents
SDK error codes
Purchase error codes
API error codes
FAQ
Recipes
Win back subscribers
Handle plan changes
Unlock the app after a web purchase
Link accounts across platforms
Handle refunds
Register devices for push
Migrate
Migrate to Voidhash
Migrate from RevenueCat
Migrate from Adapty
Migrate from Superwall
Run in observer mode
Privacy and hosting
Delete personal data
Self-hosting
Operate a self-hosted server

Handle plan changes

Let subscribers upgrade, downgrade and crossgrade on each store, and keep your app and backend in step.

A plan change moves a subscriber from one product to another: an upgrade to a higher tier, a downgrade to a lower one, or a crossgrade to the same tier with another period, such as monthly to yearly. Each store applies changes by its own rules, and Voidhash records the result.

Model plans as products

Create one product per plan, such as pro_monthly, pro_yearly and max_monthly, and attach the perks each plan grants. Your app checks perks, so a plan change needs no code to change access: the new product's perks apply once the store applies the change. See Products and perks.

How each store handles a change

StoreHow it startsWhen it takes effectWebhooks
App StoreThe app buys the new product, or the person changes plan in their Apple account settings.Upgrades at once; downgrades at the next renewal.subscription.renewed naming the new product when it takes effect. The subscription keeps its subscriptionId.
Google PlayGoogle Play's subscription replacement, which the Voidhash SDKs don't start (see below).When Google Play replaces the subscription.subscription.created for the new subscription and subscription.expired for the old one.
StripeYour backend or Stripe's customer portal changes the subscription's price.When Stripe applies the switch, now or at a scheduled date.subscription.product_changed at the switch, then subscription.renewed when it is charged.

The subscription lifecycle has the full rules.

App Store

Put every plan in one subscription group in App Store Connect. Buying another product of the group is then a plan change that Apple applies, not a second subscription:

try await voidhash.purchase(product: yearly)

A downgrade bought this way keeps the current product, and its perks, until the next renewal. People can also change plan in Apple's own subscription settings; open them with showManageSubscriptions() (Swift) or iosShowManageSubscriptions() (React Native).

Google Play

The Kotlin and React Native SDKs buy a product without replacing an existing subscription. Buying a second subscription product therefore starts a separate subscription, and both renew and bill. Before you offer another plan on Android, check the person's current subscription, and send people who want to switch to Google Play's subscription settings to cancel the old one.

When a subscription is replaced on Google Play, for example by a billing SDK you still run in observer mode, Voidhash records the new subscription and expires the old one.

Stripe

Change the subscription's price with Stripe's API or customer portal. The new price must be connected to a Voidhash product; see Connect your prices (Help center).

What your app does

  • Gate on perks. The SDK refreshes the person after each purchase, so an upgrade bought in the app unlocks at once. A change made outside the app arrives with the next refresh.
  • Show the current plan from the person's current subscription, which names its product. On the App Store, a pending downgrade still shows the current product until it takes effect.
  • Treat USER_CANCELLED as no change. Nothing was bought.

What your backend does

  • Don't treat subscription.created as a new customer. On Google Play it also marks a plan change, followed by subscription.expired for the replaced subscription.
  • Don't end access on a single subscription.expired. The person may hold another active subscription. Check access for the person instead of deriving it from one event.
  • Expect events out of order. Compare occurredAt before you overwrite newer state.

When something goes wrong

SymptomCauseFix
A subscriber pays for two plans on AndroidA second subscription product was bought without cancelling the first.Send them to Google Play's subscription settings to cancel one.
The new plan grants nothingIts store product isn't mapped. Voidhash keeps the purchase and applies it once you map the product, within a year.Map the store product.
An App Store downgrade hasn't appliedApple applies downgrades at the next renewal.Wait for the renewal; the person keeps the higher plan until then.
A Stripe price change grants nothingThe new price isn't connected.Connect it to a product.

Test plan changes with sandbox accounts on each store before you ship; see Test purchases (Help center).

Next steps

  • Subscription lifecycle
  • Webhook events
  • Win back subscribers

Something wrong or missing on this page? Tell us by email or on Discord.

PreviousWin back subscribersNextUnlock the app after a web purchase

On this page

Model plans as productsHow each store handles a changeApp StoreGoogle PlayStripeWhat your app doesWhat your backend doesWhen something goes wrongNext steps