iosstorekitin-app-subscriptionrevenuecatstorekit2

StoreKit's isEligibleForIntroOffer returns false


I have two problems:

  1. Product.SubscriptionInfo.isEligibleForIntroOffer always returns true.
  2. product.subscription!.isEligibleForIntroOffer always returns false.

(product is a random Product with proper App Store-setup.)

Earlier I used RevenueCat, and I had the same problem. They explain here and here, that it's Apple's problem, but I didn't believe them, so first I updated to RevenueCat 4 with a painful migration, then I threw them away and used StoreKit 2. I ran into the same issue, see above.

Furthermore, RevenueCat 3 causes the same problem in production! Currently my app offers trial periods for those too, who are not eligible anymore. Is there a working way to determine the eligibility?


Solution

  • If it can be actual for someone. I tested this issue with my two different iPhones. One with subscription in the past and state .expired of RenewalState for current Product - as an additional verification.

    Debugger always shows:

    product.subscription?.isEligibleForIntroOffer -> false
    Product.SubscriptionInfo.isEligibleForIntroOffer(for: GROUP_ID) -> false
    

    But when I tested yet not subscribed device - other iPhone with no data about previous subscriptions I received other results:

    product.subscription?.isEligibleForIntroOffer -> true
    Product.SubscriptionInfo.isEligibleForIntroOffer(for: GROUP_ID) -> true
    

    What did I learn as a result? That "Clear Purchase History" for sandbox account in App Store Connect, "Clear Purchase History" in App Store app from iPhone "Settings" and "Reset Eligibility" don't work. Yes, it can only be my research, but I have such results. As a variant, we can create some different accounts and use them in different states.


    Therefore, I have my own approach to take advantage of all available opportunities:

    1. It is used to check subscriptions with available offerings. This is the actual use in Sandbox:

          product?.subscription?.introductoryOffer?.type == .introductory
      
    2. In production, I use a reworked method from Apple's documentation:

    func isEligibleForIntroductoryOffer() async {
            ///Get product & check `introductoryOffer` subscription is available for products
            guard let product = subscriptions.first(where: { $0.subscription?.introductoryOffer?.type == .introductory }) else {
                return
            }
            
            if let subscription = product.subscription {
                ///The product is eligible for an introductory offer.
                let isEligible = await subscription.isEligibleForIntroOffer
                 await MainActor.run { isUserEligible = isEligible }
                debugPrint("/n\(SKCoordinator.ID) - isEligibleForIntroOffer is \(isUserEligible)/n")
            }
        }
    

    ...using in:

    init() { 
    Task { [weak self] in
          guard let self = self else { return }
          await self.isEligibleForIntroductoryOffer()
         }
    }
    
    1. As an additional option to iterate through subscriptions:
    ///Check the `subscriptionGroupStatus` to learn the auto-renewable subscription state to determine whether the customer
    ///is new (never subscribed), active, or inactive (expired subscription). This app has only one subscription
    ///group, so products in the subscriptions array all belong to the same group. The statuses that
    ///`product.subscription.status` returns apply to the entire subscription group.
    
    let subscriptionGroupStatus = try? await subscriptions.first?.subscription?.status.first?.state
            
            if subscriptionGroupStatus {
                switch subscriptionGroupStatus {
                case .expired:              print("expired")
                case .inBillingRetryPeriod: print("inBillingRetryPeriod")
                case .inGracePeriod:        print("inGracePeriod")
                case .revoked:              print("revoked")
                case .subscribed:           print("subscribed")
                default:                    print("unknown case")
                }
            } else {
                ///if the customer is new (never subscribed)
                print("No subscriptions for now...")
            }