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
| Store | How it starts | When it takes effect | Webhooks |
|---|---|---|---|
| App Store | The 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 Play | Google 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. |
| Stripe | Your 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_CANCELLEDas no change. Nothing was bought.
What your backend does
- Don't treat
subscription.createdas a new customer. On Google Play it also marks a plan change, followed bysubscription.expiredfor 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
occurredAtbefore you overwrite newer state.
When something goes wrong
| Symptom | Cause | Fix |
|---|---|---|
| A subscriber pays for two plans on Android | A second subscription product was bought without cancelling the first. | Send them to Google Play's subscription settings to cancel one. |
| The new plan grants nothing | Its 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 applied | Apple applies downgrades at the next renewal. | Wait for the renewal; the person keeps the higher plan until then. |
| A Stripe price change grants nothing | The 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
Something wrong or missing on this page? Tell us by email or on Discord.