Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check if user has paid for an Auto Renewal Subscription In App Purchase in Swift

I currently have a Non-Consumable and an Auto Renewal Subscription In App Purchase set in iTunesConnect. My problem is that I'm not sure how to check if content can be unlock for user for the Auto Renewal Subscription. I have no problems with the Non-Consumables In App Purchases, I validate them by checkinging if the product ID exists in UserDefaults, if it does, I unlock the content otherwise I notify the user but this method doesn't work with Auto Renewal Subscription In App Purchases. When I test it, I can make the purchase transation through the App store but when I try to see if the product ID exists in UserDefaults it returns false. In fact, I manually checked if the key exists and it doesn't, it only shows keys for Non-Consumable purchases.

Here is the code I'm using.

Here is the working code I have been using for years to validate Non-Consumable in app purchases.

Here is the In App Manager class I'm using.

import UIKit
import StoreKit

protocol IAPManagerDelegate {
    func managerDidRestorePurchases()
}

class IAPManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver, SKRequestDelegate {
    static let sharedInstance = IAPManager()
    var request:SKProductsRequest!
    var products:NSArray!
    var delegate:IAPManagerDelegate?

    func setupInAppPurchases(){
        self.validateProductIdentifiers(self.getProductIdentifiersFromMainBundle())

        SKPaymentQueue.default().add(self)
    }

    func getProductIdentifiersFromMainBundle() -> NSArray {
        var identifiers = NSArray()
        if let url = Bundle.main.url(forResource: "iap_product_ids", withExtension: "plist"){
            identifiers = NSArray(contentsOf: url)!
        }
        return identifiers
    }

    func validateProductIdentifiers(_ identifiers:NSArray) {
        let productIdentifiers = NSSet(array: identifiers as [AnyObject])
        let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
        self.request = productRequest
        productRequest.delegate = self
        productRequest.start()
    }

    func createPaymentRequestForProduct(_ product:SKProduct){
        let payment = SKMutablePayment(product: product)
        payment.quantity = 1
        SKPaymentQueue.default().add(payment)
    }

    func verifyReceipt(_ transaction:SKPaymentTransaction?){
        let receiptURL = Bundle.main.appStoreReceiptURL!
        if let receipt = try? Data(contentsOf: receiptURL){

            let requestContents = ["receipt-data" : receipt.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))]

            do {
                let requestData = try JSONSerialization.data(withJSONObject: requestContents, options: JSONSerialization.WritingOptions(rawValue: 0))
                //  PRODUCTION URL
                // let storeURL = URL(string: "https://buy.itunes.apple.com/verifyReceipt")
                //  TESTING URL: Uncomment for testing InAppPurchases
                let storeURL = URL(string: "https:/sandbox.itunes.apple.com/verifyReceipt")

                var request = URLRequest(url: storeURL!)
                request.httpMethod = "Post"
                request.httpBody = requestData

                let session = URLSession.shared
                let task = session.dataTask(with: request,
                                            completionHandler: { (responseData, response, error) -> Void in
                    do {
                        let json = try JSONSerialization.jsonObject(with: responseData!, options: .mutableLeaves) as! NSDictionary
                        if (json.object(forKey: "status") as! NSNumber) == 0 {
                            if let latest_receipt = json["latest_receipt_info"]{
                                self.validatePurchaseArray(latest_receipt as! NSArray)
                            } else {
                                let receipt_dict = json["receipt"] as! NSDictionary
                                if let purchases = receipt_dict["in_app"] as? NSArray{
                                    self.validatePurchaseArray(purchases)
                                }
                            }
                            if transaction != nil {
                                SKPaymentQueue.default().finishTransaction(transaction!)
                            }
                            DispatchQueue.main.sync(execute: { () -> Void in
                                self.delegate?.managerDidRestorePurchases()
                            })
                        } else {
                            print(json.object(forKey: "status") as! NSNumber)
                        }
                    } catch {
                        print(error)
                    }
                })
                task.resume()
            } catch {
                print(error)
            }
        } else {
            print("No Receipt")
        }
    }

    func validatePurchaseArray(_ purchases:NSArray){
        for purchase in purchases as! [NSDictionary]{
            self.unlockPurchasedFunctionalityForProductIdentifier(purchase["product_id"] as! String)
        }
    }

    func unlockPurchasedFunctionalityForProductIdentifier(_ productIdentifier:String){
        UserDefaults.standard.set(true, forKey: productIdentifier)
        UserDefaults.standard.synchronize()
        DispatchQueue.main.async {
            UIApplication.shared.isNetworkActivityIndicatorVisible = false
        }
    }

    func lockPurchasedFunctionalityForProductIdentifier(_ productIdentifier:String){
        UserDefaults.standard.set(false, forKey: productIdentifier)
        UserDefaults.standard.synchronize()
        UIApplication.shared.isNetworkActivityIndicatorVisible = false
    }

    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        let inAppPurchases = response.products
        // Sort the items
        self.products = inAppPurchases.reversed() as NSArray
    }

    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions as [SKPaymentTransaction]{
            switch transaction.transactionState{
            case .purchasing:
                print("Purchasing")
                UIApplication.shared.isNetworkActivityIndicatorVisible = true
            case .deferred:
                print("Deferrred")
                UIApplication.shared.isNetworkActivityIndicatorVisible = false
            case .failed:
                print("Failed")
                //print(transaction.error?.localizedDescription)
                UIApplication.shared.isNetworkActivityIndicatorVisible = false
                SKPaymentQueue.default().finishTransaction(transaction)
            case.purchased:
                print("Purchased")
                self.verifyReceipt(transaction)
            case .restored:
                print("Restored")

            }
        }
    }

    func restorePurchases(){
        let request = SKReceiptRefreshRequest()
        request.delegate = self
        request.start()
    }

    func requestDidFinish(_ request: SKRequest) {
        self.verifyReceipt(nil)
    }
}

Here is how I'm presenting the In App Purchases in a UITableView.

class StoreTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, IAPManagerDelegate {
    @IBOutlet weak var premiumFeaturesTable: UITableView!
    @IBOutlet weak var buttonClose: UIButton!
    @IBOutlet weak var buttonRestore: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        IAPManager.sharedInstance.delegate = self
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return IAPManager.sharedInstance.products.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell =  tableView.dequeueReusableCell(withIdentifier: "cellInAppPurchase")as! CustomCellForInAppPurchasesTableViewCell
        let product = IAPManager.sharedInstance.products.object(at: indexPath.row) as! SKProduct

         cell.labelIAppItem.text = product.localizedTitle
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        IAPManager.sharedInstance.createPaymentRequestForProduct(IAPManager.sharedInstance.products.object(at: indexPath.row) as! SKProduct)
    }

    @IBAction func closeViewController(_ sender: AnyObject) {
        self.presentingViewController!.dismiss(animated: true, completion: nil)
    }

    @IBAction func restorePurchases(_ sender: AnyObject) {
        IAPManager.sharedInstance.restorePurchases()
    }
}

Here is how I'm unlocking content

if NSUserDefaults.standardUserDefaults().boolForKey("com.theAppID.app"){
    // Unlock content.
}else{
    // Notify user.
}

Again, everything works for Non-Consumables but for Auto Renewal Subscriptions I'm not sure how to unlock content after the user made purchased.

What am I missing, what is the correct way to check if the user has paid for an Auto Renewal Subscription based on the above code?

EDIT: The simplest answer is... use RevenueCat for your subscription based apps, it makes your life easier.

like image 892
fs_tigre Avatar asked May 27 '19 03:05

fs_tigre


People also ask

Do apps automatically renew subscriptions?

Subscriptions on Google Play renew automatically unless you unsubscribe. Make sure to sign in to the Google Account that has your subscriptions.

How do I know if in-app purchases are free?

In the App Store, applications with in-app purchases will have a disclaimer next to the purchase button. If the app is initially free, this note should be beside the Get button. If it's paid, the disclaimer is beside the price tag.


1 Answers

Please check this link for auto-renewal subscription.

You can use the below function from your In-App manager class.

func verifyReceipt(_ transaction:SKPaymentTransaction?) 

after validate you will get below response code and details for your last date for the subscription. Please check this link.

Note: Don't forget to pass the "password" field in receipt validation for the auto-renewal subscription.

like image 127
Mitul.Patel Avatar answered Oct 04 '22 00:10

Mitul.Patel