Home > database >  Can I check a user subscription variable Bool from a "PaywallDelegate" extension in Superw
Can I check a user subscription variable Bool from a "PaywallDelegate" extension in Superw

Time:12-09

I am integrating Superwall and RevenueCat in Swift. I have decided to use Superwall due to its ease of config and setup, but I am actually struggling to present the paywall conditionally only to users who are not subscribed to the platform. Is there any professional way to check in (isUserSubscribed) if the user is correctly subscribed?

extension PaywallService: PaywallDelegate {
   
    
  // 1
  func purchase(product: SKProduct) {
    Task {
      do {
        let result = try await Purchases.shared.purchase(product: StoreProduct(sk1Product: product))
        // React to the purchase here
          
        print(result)
      } catch {
        // Handle purchase error here
      }
    }
  }

  // 2
  func restorePurchases(completion: @escaping (Bool) -> Void) {
    Task {
      do {
        let purchaserInfo = try await Purchases.shared.restorePurchases()
        
        // check purchaserInfo to see if subscription "MyEntitlement" is now active
        let restored = purchaserInfo.entitlements.all["subPlan"]?.isActive == true
        
        completion(restored)
      } catch {
        completion(false)
      }
    }
  }

    
    
    
  // 3
    func **isUserSubscribed**()  -> Bool {
    // TODO: I need to check here if the user is correctly subscribed, but this first run always gives false and the user is ALWAYS getting the paywall. 
       
        
        var isSub:Bool = false
        Purchases.shared.getCustomerInfo  { purchaserInfo, error in
                    // access latest purchaserInfo
                    
            if (purchaserInfo?.entitlements.all["subPlan"]?.isActive == true) {
                isSub = purchaserInfo?.entitlements.all["subPlan"]?.isActive == true
                

            } else {
                isSub = false
            }
                
                }
          
        return isSub
        
    
    }
}

Many thanks!

My first run always gives false and the user is ALWAYS getting the paywall.

CodePudding user response:

The issue you've got is that Purchases.shared.getCustomerInfo has a completion block which is asynchronous. This means that the value of isSub is returned immediately before the completion block has had a chance to update isSub, which is why it's always going to be false.

To manage RevenueCat and Superwall together, we recommend creating one class that handles it all for you called PaywallManager.swift (remember to replace your API keys):

import Paywall
import RevenueCat
import StoreKit

final class PaywallManager: NSObject {
  static let shared = PaywallManager()
  var isSubscribed = false

  #warning("Replace these with your API keys:")
  private static let revenueCatApiKey = "YOUR_REVENUECAT_PUBLIC_API_KEY"
  private static let superwallApiKey = "YOUR_SUPERWALL_PUBLIC_API_KEY"

  private let proEntitlement = "subPlan"

  /// Configures both the RevenueCat and Superwall SDKs.
  ///
  /// Call this on `application(_:didFinishLaunchingWithOptions:)`
  static func configure() {
    Purchases.configure(withAPIKey: revenueCatApiKey)
    Purchases.shared.delegate = shared

    Paywall.configure(
      apiKey: superwallApiKey,
      delegate: shared
    )
  }

  /// Logs the user in to both RevenueCat and Superwall with the specified `userId`.
  ///
  /// Call this when you retrieve a userId.
  static func logIn(userId: String) async {
    do {
      let (customerInfo, _) = try await Purchases.shared.logIn(userId)
      shared.updateSubscriptionStatus(using: customerInfo)

      Paywall.identify(userId: userId)
    } catch {
      print("A RevenueCat error occurred", error)
    }
  }

  /// Logs the user out of RevenueCat and Superwall.
  ///
  /// Call this when your user logs out.
  func logOut() async {
    do {
      let customerInfo = try await Purchases.shared.logOut()
      updateSubscriptionStatus(using: customerInfo)
      Paywall.reset()
    } catch {
      print("A RevenueCat error occurred", error)
    }
  }

  /// Handles a deep link to open a paywall preview.
  ///
  /// [See here](https://docs.superwall.com/docs/in-app-paywall-previews#handling-deep-links)
  /// for information on how to call this function in your app.
  static func handleDeepLink(_ url: URL) {
    Paywall.handleDeepLink(url)
  }
}

// MARK: - Purchases Delegate
extension PaywallManager: PurchasesDelegate {
  /// Handles updated CustomerInfo received from RevenueCat.
  func purchases(_ purchases: Purchases, receivedUpdated customerInfo: CustomerInfo) {
    updateSubscriptionStatus(using: customerInfo)
  }

  /// Updates the subscription status in response to customer info received from RevenueCat.
  private func updateSubscriptionStatus(using customerInfo: CustomerInfo) {
    isSubscribed = customerInfo.entitlements.active[proEntitlement] != nil
  }

  /// Restores purchases and updates subscription status.
  ///
  /// - Returns: A boolean indicating whether the user restored a purchase or not.
  private func restore() async -> Bool {
    do {
      let customerInfo = try await Purchases.shared.restorePurchases()
      updateSubscriptionStatus(using: customerInfo)
      return customerInfo.entitlements.active[proEntitlement] != nil
    } catch {
      return false
    }
  }

  /// Purchases a product with RevenueCat.
  ///
  /// - Returns: A boolean indicating whether the user cancelled or not.
  private func purchase(_ product: SKProduct) async {
    let storeProduct = StoreProduct(sk1Product: product)
    do {
      let (_, customerInfo, _) = try await Purchases.shared.purchase(product: storeProduct)
      updateSubscriptionStatus(using: customerInfo)
    } catch {
      print("An error occurred purchasing", error)
    }
  }
}

// MARK: - Paywall Delegate
extension PaywallManager: PaywallDelegate {
  /// Purchase a product from a paywall.
  func purchase(product: SKProduct) {
    Task {
      await purchase(product)
    }
  }

  func restorePurchases(completion: @escaping (Bool) -> Void) {
    Task {
      let isRestored = await restore()
      completion(isRestored)
    }
  }

  /// Lets Superwall know whether the user is subscribed or not.
  func isUserSubscribed() -> Bool {
    return isSubscribed
  }
}

Call this in the AppDelegate:

func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
  PaywallManager.configure()
}

What this does, is it configures and sets the delegate of Purchases first before configuring Paywall. When it does this, the delegate function purchases(_:,receivedUpdated:) receives a callback with the latest CustomerInfo. From there, it determines and stores the user's subscription status before isUserSubscribed() is called.

Note that for a fresh install of your app, if you have a session_start trigger, isUserSubscribed() will still be called before RevenueCat has had a chance to update the subscription status because RevenueCat is retrieving the customer info from online. For subsequent app opens, the user subscription status will be correct. We will fix this in the next major update of our app, however, unless a user with a subscription is redownloading the app you should be fine.

  • Related