Check if an Auto-Renewable Subscription is still valid
Asked Answered
B

4

24

I would like to check the Auto Renewable Subscription status whenever I open the app.

This is to make sure that the user is still subscribed to the service. How do I achieve this?

Any thoughts? Thank you

P.S.: I am using SwiftyStoreKit

Babbage answered 14/4, 2017 at 9:20 Comment(1)
You need to validate the in app section of the receipt. Refer to the in app purchase programming guide from AppleJigging
K
12

Here is several ways to do receipt validation to check is user granted to subscription. Here is two ways of doing it correctly:

  1. Do receipt validation locally as it is written here.
  2. Do receipt validation remotely as it is written here. It is mentioned that receipt should not be sent to App Store within an app. Short summary:

    • Your app sends receipt to your backend.
    • Your backend sends receipt to Apple backend for validation.
    • Your backend gets response from the apple.
    • Your backend sends result back to your app is receipt valid or invalid.

In both ways you will get list of in-app purchases. It will contain expired subscriptions as well. You would need to go through all subscriptions and check expiration dates. If it is still valid you must grant user with subscription.

As I understand you are using SwiftyStoreKit and here is open task for local receipt validation.

Klaraklarika answered 14/4, 2017 at 11:49 Comment(4)
Yes, I am using SwiftyStoreKit, but the SwiftyStoreKit.restorePurchases does not let me check the dates of the purchases, which means I am not able to check the validity of the restored purchasedBabbage
Yes. Your are right. As well every time you will execute restorePurchases authentication (user name & password) dialog will be shown to the user.Klaraklarika
Ramis, so, it is NOT possible at all to get the date/validity of those restorePurchases ?Babbage
In my experience it is impossible.Klaraklarika
S
9

You can check with this function. its works with swift4

func receiptValidation() {
let SUBSCRIPTION_SECRET = "yourpasswordift"
let receiptPath = Bundle.main.appStoreReceiptURL?.path
if FileManager.default.fileExists(atPath: receiptPath!){
    var receiptData:NSData?
    do{
        receiptData = try NSData(contentsOf: Bundle.main.appStoreReceiptURL!, options: NSData.ReadingOptions.alwaysMapped)
    }
    catch{
        print("ERROR: " + error.localizedDescription)
    }
    //let receiptString = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
    let base64encodedReceipt = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithCarriageReturn)

    print(base64encodedReceipt!)


    let requestDictionary = ["receipt-data":base64encodedReceipt!,"password":SUBSCRIPTION_SECRET]

    guard JSONSerialization.isValidJSONObject(requestDictionary) else {  print("requestDictionary is not valid JSON");  return }
    do {
        let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
        let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt"  // this works but as noted above it's best to use your own trusted server
        guard let validationURL = URL(string: validationURLString) else { print("the validation url could not be created, unlikely error"); return }
        let session = URLSession(configuration: URLSessionConfiguration.default)
        var request = URLRequest(url: validationURL)
        request.httpMethod = "POST"
        request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
        let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
            if let data = data , error == nil {
                do {
                    let appReceiptJSON = try JSONSerialization.jsonObject(with: data)
                    print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
                    // if you are using your server this will be a json representation of whatever your server provided
                } catch let error as NSError {
                    print("json serialization failed with error: \(error)")
                }
            } else {
                print("the upload task returned an error: \(error)")
            }
        }
        task.resume()
    } catch let error as NSError {
        print("json serialization failed with error: \(error)")
    }



}
}
Spinelli answered 6/11, 2017 at 21:49 Comment(3)
How do I get SUBSCRIPTION_SECRET?Gavan
Yeah, and nothing about actual receipt expiration. You neither granting permissions, nor restricting the access in this piece of codeCamerlengo
Directly sending a request from App to AppStore is not recommended. The signature validation is enough from the app side. "Warning Do not call the App Store server verifyReceipt endpoint from your app. You can't build a trusted connection between a user’s device and the App Store directly, because you don’t control either end of that connection, which makes it susceptible to a man-in-the-middle attack." developer.apple.com/documentation/storekit/in-app_purchase/…Robtrobust
M
4

I wanted to provide an alternative solution that uses the RevenueCat SDK for those who still stumble upon this question.

AppDelegate.swift

Configure the RevenueCat Purchases SDK with your api key an optional user identifier.

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

    Purchases.configure(withAPIKey: "<...>", appUserID: "<...>")

    ...

    return true
}

Subscription status function

The function below checks the PurchaserInfo to see if the user still has an active "entitlement" (or you can check for an active product ID directly).

func subscriptionStatus(completion: @escaping (Bool)-> Void) {
    Purchases.shared.purchaserInfo { (info, error) in

        // Check if the purchaserInfo contains the pro feature ID you configured
        completion(info?.activeEntitlements.contains("pro_feature_ID") ?? false)

        // Alternatively, you can directly check if there is a specific product ID
        // that is active.
        // completion(info?.activeSubscriptions.contains("product_ID") ?? false)
    }
}

Getting subscription status

You can call the above function as often as needed, since the result is cached by the Purchases SDK it will return synchronously in most cases and not require a network request.

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    subscriptionStatus { (subscribed) in
        if subscribed {
            // Show that great pro content
        }
    }
}

If you're using SwiftyStoreKit, the RevenueCat syntax is fairly similar and there is a migration guide available to help switch over.

Mandelbaum answered 28/3, 2019 at 17:59 Comment(1)
RevenueCat has saved me an insane amount of time when developing my subscription-based app. Highly recommended!Merlinmerlina
B
-3

Yet another solution to handle auto-renewable iOS subscription using Qonversion SDK.

AppDelegate.swift

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    Qonversion.launch(withKey: "yourProjectKey")
    
    
    return true
  }

Get subscription status

Link App Store subscription to Qonversion Product, and link the Product to Permission. Then you just need to trigger checkPermissions method at the start of your app to check if a user's subscription is still valid. This method will check the user receipt and will return the current permissions. And then for the still-active subscription, you can get the details if the subscriber has turned-off auto-renewal, if he is in grace period (billing retry state), etc.

Qonversion.checkPermissions { (permissions, error) in
  if let error = error {
    // handle error
    return
  }
  
  if let premium = permissions["premium"], premium.isActive {
    switch premium.renewState {
       case .willRenew, .nonRenewable:
         // .willRenew is the state of an auto-renewable subscription 
         // .nonRenewable is the state of consumable/non-consumable IAPs that could unlock lifetime access
         break
       case .billingIssue:
         // Grace period: permission is active, but there was some billing issue.
         // Prompt the user to update the payment method.
         break
       case .cancelled:
         // The user has turned off auto-renewal for the subscription, but the subscription has not expired yet.
         // Prompt the user to resubscribe with a special offer.
         break
       default: break
    }
  }
}

You can check our sample app that demonstrates auto-renewable subscription implementation here.

Bestead answered 29/10, 2020 at 15:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.