Solved

Is there a way to know if a user previously had a free trial on iOS?

  • 26 October 2021
  • 3 replies
  • 593 views

If a user subscribes to a free trial, cancels that free trial, and then later attempts to initiate a free trial again for the same product, they are immediately (and unexpectedly) charged by Apple. Our app has language around free trials, and we’d love to find a way to dynamically update this language to no longer mention the free trial once the user is no longer eligible under these conditions. Is there any way for us to know whether a user is, according to Apple, eligible for a free trial?

icon

Best answer by matthew_l 28 October 2021, 04:53

View original

3 replies

Userlevel 1
Badge

I have the same question as @Jared, so I’ll top up on it here.

The RevenueCat documentation says that for Introductory Offers (free trials), the “SDK applies offer to purchases automatically”. The RC sample app unwraps package.product.introductoryPrice to see if price == 0 to do the kind of dynamic language that @Jared and I am looking for.

 

RevenueCat sample app’s code:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PackageCell", for: indexPath) as! PackageCell

/// - Configure the PackageCell to display the appropriate name, pricing, and terms
if let package = self.offering?.availablePackages[indexPath.row] {
cell.packageTitleLabel.text = package.product.localizedTitle
cell.packagePriceLabel.text = package.localizedPriceString

if let intro = package.product.introductoryPrice {
if intro.price == 0 {
cell.packageTermsLabel.text = "\(intro.subscriptionPeriod.periodTitle()) free trial"
} else {
cell.packageTermsLabel.text = "\(package.localizedIntroductoryPriceString) for \(intro.subscriptionPeriod.periodTitle())"
}
} else {
cell.packageTermsLabel.text = "Unlocks Premium"
}
}

return cell
}

 

Unfortunately, in my sandbox testing, I keep getting back non-nil values for introductoryPrice, which then triggers the free trial language to the customer. Tapping on that purchase button brings up an App Store payment sheet that shows the standard price.

Is the implementation in the sample app correct for dynamically showing free trials when they’re available (and not already used/expired), or is there something else we need to do to properly process the package?

Userlevel 1
Badge

While the Quickstart guide, documentation, and sample app all suggest the above should work, I didn’t want to submit an app that I knew didn’t show offers properly, so I kept on digging for a working solution.

This thread made me aware of the checkTrialOrIntroductoryPriceEligibility method, so I played with that and found it did exactly what I needed. Here is the basic idea of how I used it my SwiftUI app:

class SubscriptionManager: ObservableObject {

@Published var packages: [Purchases.Packages]? = nil
@Published var trialEligibility: [String: RCIntroEligibility]?

/// Sets the RevenueCat current offerings to the @Published packges property.
func loadPackages() {
// clear any existing values to make sure we're using the latest from RC
packages = nil
trialEligibility = nil

var productIDs: [String] = []

Purchases.shared.offerings { offerings, error in
if let packages = offerings?.current?.availablePackages {
for package in packages {
productIDs.append(package.product.productIdentifier)
}

Purchases.shared.checkTrialOrIntroductoryPriceEligibility(productIDs) { eligibility in
self.trialEligibility = eligibility
}

self.packages = packages
}
}
}
}

 

In my SwiftUI views, I pass each purchase button a package to process and ask it check to see if the offer is eligible for intro pricing.

private var introEligible: Bool {
subscriptionManager.trialEligibility?[package.product.productIdentifier]?.status == .eligible
}

If the product is eligible, then I parse the pricing similar to the approach in the sample app.

This has worked as expected in sandbox testing, including when I reset purchase history (and thus intro offer eligibility) in App Store Connect.

in an attempt to minimize waiting for offers to load in when the user hits the paywall, I call loadPackages() when the app launches as well as when the paywall sheet pops up (where it’ll hopefully load quickly from cache).

Hopefully this helps. If anyone notices anything I’m doing incorrectly, please let me know :)

Badge

While the Quickstart guide, documentation, and sample app all suggest the above should work, I didn’t want to submit an app that I knew didn’t show offers properly, so I kept on digging for a working solution.

This thread made me aware of the checkTrialOrIntroductoryPriceEligibility method, so I played with that and found it did exactly what I needed. Here is the basic idea of how I used it my SwiftUI app:

class SubscriptionManager: ObservableObject {

@Published var packages: [Purchases.Packages]? = nil
@Published var trialEligibility: [String: RCIntroEligibility]?

/// Sets the RevenueCat current offerings to the @Published packges property.
func loadPackages() {
// clear any existing values to make sure we're using the latest from RC
packages = nil
trialEligibility = nil

var productIDs: [String] = []

Purchases.shared.offerings { offerings, error in
if let packages = offerings?.current?.availablePackages {
for package in packages {
productIDs.append(package.product.productIdentifier)
}

Purchases.shared.checkTrialOrIntroductoryPriceEligibility(productIDs) { eligibility in
self.trialEligibility = eligibility
}

self.packages = packages
}
}
}
}

 

In my SwiftUI views, I pass each purchase button a package to process and ask it check to see if the offer is eligible for intro pricing.

private var introEligible: Bool {
subscriptionManager.trialEligibility?[package.product.productIdentifier]?.status == .eligible
}

If the product is eligible, then I parse the pricing similar to the approach in the sample app.

This has worked as expected in sandbox testing, including when I reset purchase history (and thus intro offer eligibility) in App Store Connect.

in an attempt to minimize waiting for offers to load in when the user hits the paywall, I call loadPackages() when the app launches as well as when the paywall sheet pops up (where it’ll hopefully load quickly from cache).

Hopefully this helps. If anyone notices anything I’m doing incorrectly, please let me know :)

@matthew_l That's exactly what I'm looking for, thank you. What exactly have you configured on AppStoreConnect and RevenueCat?
Did you just add an introductory offer to your auto-renewable subscription?

Best Regards

Reply