We are designing an application that will have recurring application charges every month (30 days interval). Let’s assume we have three plans: Basic, Advanced and Pro. We understand how to create an app subscription with appSubscriptionCreate for the initial install of the application.
However, we have some questions regarding upgrading and downgrading plans:
If the store is currently on Basic and they choose to upgrade to Advanced mid-cycle, how do we ensure that the billing cycle flow will remain the same (e.g. if they upgraded 20 days in the first billing cycle, how do we keep the end of this billing cycle after 10 days)? We also want to charge them instantly the value of the Advanced plan - the value of the Basic plan (as if they started with the Advanced in the first place). Will the replacementBehavior set to STANDARD be enough to keep it the same, if we change the price of the plan and add a discount?
If the store is currently on Advanced and wants to downgrade to Basic, can we use the replacementBehavior set to APPLY_ON_NEXT_BILLING_CYCLE to make sure that they will charged with the Basic plan’s value on the start of the next billing cycle, and keep the billing cycle flow the same?
For mid-cycle upgrades, replacementBehavior: STANDARD handles exactly what you described. Your original billing cycle dates will preserved while calculating prorated charges, so if they upgrade 20 days into a 30-day cycle, the billing cycle end stays the same and they’re charged the prorated difference immediately. No manual discounts needed.
For downgrades, APPLY_ON_NEXT_BILLING_CYCLE is perfect for keeping merchants on their current higher-tier plan until the cycle ends. The Advanced plan continues running with full access, no credits are issued, and the Basic plan only kicks in at the next billing cycle start. This keeps your billing timing consistent while letting merchants get full value from what they already paid for.
Thank you for this response! The thing is, we notice that the new app subscription has a different currentPeriodEnd timestamp from the previous app subscription, even if we use replacementBehavior: APPLY_ON_NEXT_BILLING_CYCLE. Are we supposed to keep track of the billing cycles end dates on our own, or is there some other attribute we should we be looking for? We are experimenting with test subscriptions, of course.
Thanks for getting back to me. This is a limitation with tests in the Billing API. It doesn’t actually create a billing record, making the replacement behaviour a bit hard to test.
So we can assume that in production, our app will work as expected if we do upgrades and downgrades as we described (in upgrade with replacementBehavior: STANDARD, no manual discount needed; in downgrade with replacementBehavior: APPLY_ON_NEXT_BILLING_CYCLE)?
I just want to note as well, if you aren’t planning on using usage charges, the behaviour you are looking for is standard with Managed billing. That would save you from having to use the Billing API at all.
One last question, assuming we proceed with managed app pricing!
Concerning the initial app subscription, we know how to redirect the merchant to the plan selection page, as mentioned in the Managed App Pricing documentation.
Once the merchant has subscribed, and can now fully access the application, are we obliged to show them an unsubscribe button/link in the pages of our app? Will the merchant be able to see a similar action button in the settings of the application (Settings > Apps & Sales Channels)? Or will they have to uninstall the app entirely, to unsubscribe from the app?
With managed app pricing, you’re not required to provide unsubscribe buttons within your app interface. However, adding cancellation/downgrade controls in your app UI can improve user experience.
Downgrade buttons won’t appear in the admin UI outside of your app UI.
I’m testing the downgrading plan use case with managed pricing, moving from a ‘paid’ plan to a free plan results in an immediate downgrade when checking the current subscription using graphQL, my understanding is that this should be deferred to the next billing cycle.
query {
appInstallation {
activeSubscriptions {
name
status
}
}
}
Is this because i’m on a dev store and they are all test charges so ‘free’ anyway, or am i looking in the wrong spot for the plan the customer is currently subscribed to?
Hey @Min_Liu, part of what you’re seeing is a dev store limitation. Test subscriptions don’t create real billing records, so the deferral doesn’t work there. Same limitation I mentioned earlier in this thread about test charges.
One thing to know for production though: the subscription object in the API updates immediately when a merchant downgrades. A merchant can only have one active subscription at a time, so when they accept the downgrade, activeSubscriptions reflects the free plan right away. The deferral is specifically about billing. The merchant has paid through currentPeriodEnd, so use that timestamp to gate feature access instead of the subscription name.
Another developer ran into this exact scenario with managed pricing and I walked through it in detail here:
Thanks for the feedback, i think there will need to be some way for merchants to better manage this via managed pricing:
there is no way for managed pricing to switch to an immediate downgrade and issue a pro-rata refund
there is also no way to notify the customer on the plans page that the plan is changed immediately and they won’t receive a pro-rata refund either
The currentPeriodEnd is set to null once the appSubscription is cancelled.
For apps which run mostly behind the scenes where there are no regular interactions, this would mean that as well as subscribing to app_subscriptions/update (i haven’t tried this yet but i assume managed pricing will trigger this webhook too?), we also need to query every single store every month to get an updated currentPeriodEnd time as the webhook doesn’t give us any information about the previous subscription and by the time the webhook fires the currentPeriodEnd would have already been blanked out on the last one. It kinds of kills the benefits of having managed pricing as it’s not really well managed.
Hey @Min_Liu, the currentPeriodEnd on the cancelled subscription is null because the API only returns that field for active subscriptions. But a downgrade doesn’t start a new billing cycle, so the active subscription (the free plan) still has the original currentPeriodEnd from the paid plan’s cycle. That’s the date you’d use to gate feature access.
The reason this looks wrong on a dev store is the same test charge limitation from earlier in this thread. Test charges don’t create real billing records, so the cycle dates won’t reflect production behavior.
For monitoring changes, subscribe to the APP_SUBSCRIPTIONS_UPDATE webhook. Managed pricing does trigger it. That tells you which merchants made a plan change so you can plan for the billing transition at the end of their cycle. Occasionally run a reconciliation query too, to catch any missed events.