Plan changes and linked purchase tokens
When a Google Play subscriber upgrades, downgrades, or re-subscribes, Play does not edit the old purchase. It issues a new purchase token and points it back at the one it replaced. Getting the entitlement right means following that pointer exactly once.
What Play hands you
The plan change itself is launched from your app through Google Play Billing, with the replacement mode you choose; Play calculates any proration. Tierux's part starts when the new token reaches the server. Its subscriptionsv2 resource carries a linkedPurchaseToken naming the superseded token, and Tierux reads that field from the query response.
Tierux does not rank your plans or decide which change is an upgrade. Each token's freshly verified Play state is treated as authoritative, and the old entitlement is retired based on that state alone.
Two routes to the same result
The new token can reach Tierux first through either input, and both are handled.
Your app verifies the new token
The usual path: after the purchase completes, the client sends the new token to the verify endpoint. Tierux queries Play, sees linkedPurchaseToken, and looks up the purchase it previously recorded for the old token. Then:
- The old purchase belongs to a different user → the call is rejected with
409 TOKEN_ALREADY_LINKED_TO_ANOTHER_USERbefore anything is written. Entitlements do not move between subscribers. - The old purchase belongs to this user → this is a paying subscriber changing plans, so the free-tier subscriber cap is not applied to it.
- No old purchase on record (for example, the original purchase predates your Tierux integration) → the token is treated as a fresh grant.
Play's notification arrives first
A SUBSCRIPTION_PURCHASED notification (or a recovered, renewed, or restarted one) can carry the new token before your app has verified it. Tierux has no purchase record for that token yet, so for those four types it queries Play for the token's own state, follows linkedPurchaseToken back to the recorded purchase, and carries the user and entitlement forward from it. It then stores a purchase record for the new token — with an encrypted copy of the token so later refreshes can re-verify it — and points the entitlement at the new token.
If that Play query fails, Tierux cannot tell a plan change from an unknown token, so the RTDN endpoint answers 503 and Pub/Sub redelivers. A token that Play links to nothing Tierux recorded is acknowledged as an unknown purchase. Before changing the entitlement, Tierux re-checks that the new token is not already claimed by a different user; if it is, the change is skipped and logged.
Retiring the old entitlement
On either route, the superseded entitlement is switched off once the new token verifies as live:
// src/services/entitlementService.ts (verify path) if (verification.linkedPurchaseToken && active) { await supersedeLinkedPurchase( this.store, input.appId, verification.linkedPurchaseToken, mapping.entitlementId, input.userId, ); }
Retiring writes the old entitlement to cancelled with access off. Three cases leave it alone:
- Same entitlement. If the old and new products map to the same entitlement id — monthly Pro to annual Pro — nothing is cancelled; the one entitlement is simply re-pointed at the new token.
- Different owner. If the old purchase's recorded user is not the user this change is for, nothing is touched and a warning is logged.
- New token not yet live. On a deferred downgrade to a product mapped to a different entitlement, Play reports the new token as not yet active. The higher entitlement is left on, and lapses at its own expiry through the normal notification and verification flow.
Which products map to which entitlement ids is your product mapping — chapter 5 of the verification guide covers it. A cross-entitlement change (Basic to Pro) cancels Basic and activates Pro; a same-entitlement change keeps one record.
Late events on the old token
After a plan change, Play can still send notifications about the old token: an expiry, or a voided purchase for the prorated remainder of an upgrade. If they were applied, they would switch off the subscriber's new plan.
Tierux compares a Google Play notification's token with the token the entitlement currently points at. An event whose token hash differs is acknowledged with outcome superseded_purchase and makes no change. This guard is what makes the re-pointing step above load-bearing, and it sits in front of the idempotency and ordering checks described in the notifications chapter.
On the App Store
Apple models plan changes within a subscription group and has no Play-style linkedPurchaseToken. Tierux treats Apple's renewal-preference change notification as informational: it writes nothing to the entitlement. App Store support is implemented with limitations — see the platform support matrix.
Glossary: linkedPurchaseToken
The field, and why it is not present on every return to a subscription.
Glossary: Proration
Play's replacement modes and how they decide timing and charge.
Blog: How plan changes really work
The same mechanism told as a story, with the failure modes it prevents.
Guide: Mapping products to entitlements
Why a same-entitlement change keeps one record and a cross-entitlement change swaps two.
Feature: Purchase verification
Verifying the initial purchase and each plan change server-side.
Plan changes without double entitlements
Free tier — unlimited apps, 1 paywall. No credit card, no revenue share.
Start free