Solved

Eligibility of introductory offers


Badge +1

Hi guys,

I complete setup for product and introductory offers for it on AppStore connect. Now I check eligibility for new user (by checkTrialOrIntroductoryPriceEligibility), and completion block return dictionary with eligibility statuses and associated products. My problem is that status for all products is “Not eligible for trial or introductory price.” Why SDK return wrong status?

icon

Best answer by cody 21 July 2021, 17:09

View original

36 replies

Userlevel 5
Badge +8

@manuel-becker happy to help! Don’t hesitate to reach out again if there’s anything else we can help with

Badge +3

@manuel-becker even though I don’t have an ETA, I’ve taken note to let you know the minute we have a beta for Capacitor 

So nice of you, thank you. I still have to get it done asap because I would love to be part of the next RC “Roast my Paywall” series but mostly because users are asking a lot for trials and not having them results in lots of 1 star ratings although the app gives away so much for free already. But that’s simply how the “app store game” works I guess ;-)

I would still love to know though because I would probably at least use it for some time to do AB testing with it. Thank you again for being so responsive here. This makes all the difference for developers like me and I highly appreciate it!

Userlevel 5
Badge +8

@manuel-becker even though I don’t have an ETA, I’ve taken note to let you know the minute we have a beta for Capacitor 

Userlevel 5
Badge +8

@lucksp agreed! Sadly eligibility is ultimately managed by the OS, so we can’t do much on our side to make it better. 

 

However, for testing, here’s my advice: 

  • TestFlight for intro eligibility is… well, annoying, I’d use either Sandbox or StoreKit Configuration files. 
  • If you’re testing on Sandbox, you can either create multiple sandbox accounts and switch between them (that’s what I do), or use a single one, but then go into App Store Connect, find the sandbox user (Users and Accounts → Sandbox), then click Edit on the top right, select the user and click Clear Purchase History. This can take a while to take effect, though (from 1 hr to 24 hrs!). You can also access a similar option through the device in Settings → App Store → Sandbox Account. 
  • If you’re testing with StoreKit Configuration files, you can actually reset eligibility very easily! You just go to Xcode → Manage transactions → delete them all. This deletes the history for the user as the app sees it… But you’ll need to also use a new appUserID in RevenueCat since sadly we don’t have a way to know that you deleted the history for the user. If you’re using anonymous IDs, a fresh install does the trick. This is the easiest way to test, but it requires a bit more setup at first. 

Hope this helps! 

Badge +3

@Andy Great to know, so the check is for my usecase redundant but still fine for me. I feel it’s so hard to find info about details like that.

Yeah I saw the release of the Paywall feature and immediately was super hyped and wanted to use it (especially because of the AB testing and saving me a lot of time) but then I recognized that it’s not available yet for Capacitor. ;-)

Badge +6

this is confusing for testing...it would be nice if on TestFlight, the sandbox users would have a reset eligibility to verify the intro offers are available.

Userlevel 5
Badge +8

If they’re in the same subscription group, and they both have free trials, then the value will be the same for the two. 

 

I forgot to mention, but if you happen to be on React-Native, we’re currently in beta with our own Paywalls, which take care of the intro eligibility handing automatically for you (along with other goodies like remotely making changes and A/B pricing experiments)

 

Here’s more info if you’re interested!

https://github.com/RevenueCat/react-native-purchases/releases/tag/7.4.0-beta.2

And if you’re on a different javascript-based SDK like Cordova / Capacitor, we’re going to work on adding support for those soon, although I don’t have an ETA just yet. 

Badge +3

@Andy Awesome, this helps a lot and I highly appreciate you taking the time to help. So for iOS the check right now asks for multiple product ids which is why I did the additional check afterwards because I wanted to get a single boolean result first to show a trial button directly on the products feature. When I understand you correctly it probably doesn’t even matter to add the second product id for the case when they are in one subscription group, right?

I will definitely check the subscription testing guide tomorrow, thank you very much.

Userlevel 5
Badge +8

Hey, sorry for the delay. 

 

For iOS: 

 

Eligibility works on a per-product basis, so I’d recommend not trying to have a single `isEligibleForTrial` boolean if you intend to display multiple products. Instead, you should think about it as “is eligible for a free trial of the monthly option” vs “is eligible of a free trial for the annual option”. 

There’s some extra nuance (I’ll go into it in a bit), but in general terms, you should just rely on the value that RevenueCat returns without doing any further computation. We’ll take care of de-duplication such that a user doesn’t get two free trials (one for annual and one for monthly). 

 

The gnarly details: 

Eligibility is actually performed on a per-subscription group basis, technically. This means that if your monthly and your annual options are in the same subscription group, and a user uses the free trial for one of them, they won’t be able to use another free trial for the other product (or for the same product). 

If products are set up to be in different subscription groups in App Store Connect, and each of them have a free trial, then yes, the user will be able to use a free trial more than once. But even if that was the case, you shouldn’t do any further computation, since ultimately it’s iOS that decides whether to grant the user a free trial or not.

So even if your logic says that the user already redeemed a free trial for another product in a different subscription group and you show a price, iOS will still automatically apply a free trial if the user is eligible.

 

My advice is to simply rely on the value that RevenueCat provides, and make sure to tie it to each product (so if you have a purchase button, for example, change the copy to “start free trial” for example if the eligibility for the currently-selected product was “eligible”. 

 

For Android: 

 

Your Android logic looks fine (I think you can skip the price == 0 check, but I also don’t think it’ll hurt). We do have a section dedicated to testing this on Android in our guide that might be helpful here: https://www.revenuecat.com/blog/engineering/the-ultimate-guide-to-android-subscription-testing/#free-trials

 

You should also be mindful of the setting in Google Play for who is eligible for a free trial from there. 

 

Hope this helps! 

Badge +3

I think I finally found a working solution which is checking for trial eligibility. Since I am not sure how exactly iOS handles this, I am testing for all returned introEligibilityStatuses and a user is eligible for me if he/she is eligible for all products while excluding the products that might not have a trial which is why I am testing for INELIGIBLE and UNKNOWN and not the other way around.

For Android it seems that an entirely fresh Play Account is needed. Important to know is that you could have been subscribed quite a few times to the app with the current RevenueCat ID and only the Play Account matters to be eligible. If you care about that you would need to check for that as well if a user has been subscribed in the past.

The following code is using TypeScript and Capacitor but the approach might be similar for other tech stacks.

/**
* Checks if the user is eligible to see a trial or intro discount. Will return true if the user is eligble but none of the current offerings have a trial or intro discount.
*/
async isEligibleForTrial(): Promise<boolean> {
const offering = (await this.getOfferings()).current,
productIdentifiers = [];

if (offering.availablePackages === null) {
return false;
}

for (const availablePackage of offering.availablePackages) {
productIdentifiers.push(availablePackage.product.identifier);
}

if (this.platform.is('ios')) {
// Purchases.checkTrialOrIntroductoryPriceEligibility is only available for iOS
const introEligibilityStatuses = await Purchases.checkTrialOrIntroductoryPriceEligibility({
productIdentifiers: productIdentifiers
});

// a user is only eligible if he is eligible for all products (prevents a user from using a monthly trial after an annual trial ran out)
let isNotEligibleCount = 0;
for (const id of productIdentifiers) {
isNotEligibleCount +=
introEligibilityStatuses[id].status === INTRO_ELIGIBILITY_STATUS.INTRO_ELIGIBILITY_STATUS_INELIGIBLE ||
introEligibilityStatuses[id].status === INTRO_ELIGIBILITY_STATUS.INTRO_ELIGIBILITY_STATUS_UNKNOWN
? 1
: 0;
}
return isNotEligibleCount === 0;
} else if (this.platform.is('android')) {
// a user on Android is eligible if one of the subscription options has a freePhase entry (it's always null if the user isn't eligible)
for (const availablePackage of offering.availablePackages) {
for (const subscriptionOption of availablePackage.product.subscriptionOptions) {
if (subscriptionOption.freePhase !== null && subscriptionOption.freePhase.price.amountMicros === 0) {
return true;
}
}
}
}

// for now we only check the status for iOS and Android, anything else is false by default
return false;
}

 

Badge +3

@Andy I am so sorry to disturb again but do you have any hint on how to test this (because my pricingPhases is always only having 1 entry which is the subscription itself and I guess it’s because the Play Account had a subscription already and when I use a fresh Google Play account it’s also not shown maybe because there needs to be a payment method attached?).

Alternatively it would help a lot already to know how the answer would look like because mine is only looking like this:

Thank you so much, I really appreciate it and if it helps and I know that it works I am happy to post my code here for the both platforms check so that others might benefit from it as well. Testing trial is a lot harder than expected though… ;-)

Manuel

Badge +3

@Andy No worries, you don’t have to be sorry. Anything helps and any hint is highly appreciated, especially with something like that which probably affects all RC’s users which are developing not natively.

That definitely sounds like a big step forward in terms of not having to sync purchases first. I don’t know yet how to get SubscriptionOption but I will deep dive the Capacitor documentation and hopefully find out. This should also prevent users from simply creating a new account and using the next free trial again because it’s iOS and Android which decides about the eligibility, right?

Thank you so much as always for all your efforts and for doing such a fantastic job, I appreciate it a lot.

Userlevel 5
Badge +8

@manuel-becker sorry for the lack of an update. 

 

We didn’t end up adding this new API, since on Android since the introduction of BillingClient 5, the answer to whether your user is eligible for an intro offer is: yes, as long as they can see them. 

 

This means that anything you get through getOfferings, the user is eligible for on Android. This is a different story from iOS. 

 

I’m thinking of ways to make this behavior more standardized across the board, although there are technical limitations that make that a bit tricky. 

 

For the time being, I’d advise: 

  • on iOS, use the API for intro eligibility
  • on Android, you can look for the `freePhase` on the PricingPhases within SubscriptionOption, if there’s one there it means the user is eligible for a free trial

 

Badge +3

@Jonah I’m sorry I missed your questions!! I’m super late to reply, but: `package.storeProduct.discounts` relates to Promotional Offers, whereas `checkTrialOrIntroDiscountEligibility` relates to Introductory Offers. To get the Introductory Offers, use `package.storeProduct.introductoryDiscount`. 

 

@Petri `checkTrialOrIntroDiscountEligibility` is currently not available in our Android API, although we do have plans to introduce it later this year. 

 

@Andy Are there any updates on this already? I am using Capacitor and just trying to implement Trials but checking if somebody is eligible seems still quite sensitive to errors, especially because Android needs a workaround. Thank you as always for your great work, I highly appreciate it.

Badge

@skuske I asked my question above in a support ticket and got a helpful answer from Michael at Developer Support.

The receipt not being available is most commonly seen in Sandbox and is not usually an issue in production. So this case of not having a receipt generated causing the eligibility to be off would be less common to see in production. In production, a receipt is generated when a user downloads the app and is saved against the Apple account.

 

So it seems that most, if not all, users on production will have a receipt and therefore return “eligible” or “ineligible”.

 

If there is no receipt for any reason, the method will always return “unknown”.

I’ll just go with this and hope for the best. Thank you! 

Badge +5

@ian-l 

Many thanks, that helps a lot!

Userlevel 1

@skuske I asked my question above in a support ticket and got a helpful answer from Michael at Developer Support.

The receipt not being available is most commonly seen in Sandbox and is not usually an issue in production. So this case of not having a receipt generated causing the eligibility to be off would be less common to see in production. In production, a receipt is generated when a user downloads the app and is saved against the Apple account.

 

So it seems that most, if not all, users on production will have a receipt and therefore return “eligible” or “ineligible”.

 

If there is no receipt for any reason, the method will always return “unknown”.

Badge +5

@cody

I would also like to know what happens for new users who don’t have any receipt on their device … 

Userlevel 1

@cody I am attempting to implement checkTrialOrIntroductoryPriceEligibility in our app using RC and just wanted to check this statement:

 

The checkTrialOrIntroductoryPriceEligibility method takes the existing receipt on your device and checks the transactions to determine if the user is eligible or not for the product ID in question. If there’s no receipt available, this method will return ‘unknown’.

 

Does that mean that it’s likely all new users will get an “unknown” eligibility response? (I am testing with a new sandbox account and get “unknown” but wanted to check if this is also the case for real-world production users)

Thanks in advance!

Userlevel 5
Badge +8

@Bilance you should be able to get eligibility information by following the steps outlined here: 

Note that the reply refers to purchaserInfo, however, on Flutter v4 or higher, the object is called customerInfo. The steps should otherwise remain the same. 

Badge +4

@Jonah I’m sorry I missed your questions!! I’m super late to reply, but: `package.storeProduct.discounts` relates to Promotional Offers, whereas `checkTrialOrIntroDiscountEligibility` relates to Introductory Offers. To get the Introductory Offers, use `package.storeProduct.introductoryDiscount`. 

 

@Petri `checkTrialOrIntroDiscountEligibility` is currently not available in our Android API, although we do have plans to introduce it later this year. 


Is there some other way to to check eligibility if checkTrialOrIntroDiscountEligibility is not available on Android? (we are using the Flutter SDK)

Userlevel 5
Badge +8

@Jonah I’m sorry I missed your questions!! I’m super late to reply, but: `package.storeProduct.discounts` relates to Promotional Offers, whereas `checkTrialOrIntroDiscountEligibility` relates to Introductory Offers. To get the Introductory Offers, use `package.storeProduct.introductoryDiscount`. 

 

@Petri `checkTrialOrIntroDiscountEligibility` is currently not available in our Android API, although we do have plans to introduce it later this year. 

Badge +4

Is this available in Android SDK? I could not find anything related to `checkTrialOrIntroDiscountEligibility` in the Android API?

Badge +3

.

Badge +3

Hi @Andy  - I’m now on latest iOS SDK (4.12.0). I’m using a SKProduct that has a free trial. `checkTrialOrIntroDiscountEligibility` is returning elligible, however. `package.storeProduct.discounts` is empty. 

 

Is this expected?

Reply