Thursday, 18 February 2021

In-App Purchase (AutoRenewable)

 Add in DidiFinishLaunching for Check In app Purchase available or not

InAppManager.shared.checkSubscriptionAvailability { (success) in            print("Subscription Available : ",success)

            

        }



To check Plan 


 override func viewWillAppear(_ animated: Bool) {

        InAppManager.shared.checkSubscriptionAvailability { (success) in 

          print("Subscription Available : ",success)

           let planWeek = InAppManager.shared.purchasedProduct == ProductType.weekly


        }


    }



//Common Class In App Purchase 


//

//  InAppManager.swift

//

//


import Foundation

import StoreKit




var AlreadySubscribed : Bool {

    set{

        UserDefaults.standard.setValue(newValue, forKey: "AlreadySubscribed")

    }

    get{

        return UserDefaults.standard.bool(forKey: "AlreadySubscribed")

    }

}

//Weekly 3.99

//Yearly 39.99

enum ProductType: String {

    case weekly = "com.ScannerPlus.Weekly"

    case yearly = "com.ScannerPlus.Yearly"

    

    static var all: [ProductType] {

        return [.weekly,.yearly]

    }

}


enum InAppErrors: Swift.Error {

    case noSubscriptionPurchased

    case noProductsAvailable

    

    var localizedDescription: String {

        switch self {

        case .noSubscriptionPurchased:

            return "No subscription purchased"

        case .noProductsAvailable:

            return "No products available"

        }

    }

}


protocol InAppManagerDelegate: class {

    func inAppLoadingStarted()

    func inAppLoadingSucceded(productType: ProductType)

    func inAppLoadingFailed(error: Swift.Error?)

    func subscriptionStatusUpdated(value: Bool)

    func inAppPurchased(value:Bool)

}


class InAppManager: NSObject {

   // #if DEBUG

    let verifyReceiptURL = "https://sandbox.itunes.apple.com/verifyReceipt"

   // #else

   // let verifyReceiptURL = "https://buy.itunes.apple.com/verifyReceipt"

   // #endif

    

   let AppShared_Secret = "3e71e091970a4cdc8fbcaaa2be4687f8"

    

    static let shared = InAppManager()

    

    weak var delegate: InAppManagerDelegate?

    

    var products: [SKProduct] = []

    

    var isTrialPurchased: Bool?

    var expirationDate: Date?

    var purchasedProduct: ProductType?

    

    var isSubscriptionAvailable: Bool = true

        {

        didSet(value) {

            self.delegate?.subscriptionStatusUpdated(value: value)

        }

    }

    

    func startMonitoring() {

        SKPaymentQueue.default().add(self)

        self.updateSubscriptionStatus()

    }

    

    func stopMonitoring() {

        SKPaymentQueue.default().remove(self)

    }

    var paymentProductType : ProductType?

    func loadProducts(type:ProductType) {

        paymentProductType = type

        let productIdentifiers = Set<AnyHashable>([type.rawValue]) as! Set<String> //Set<String>(ProductType.all.map({$0.rawValue}))

        let request = SKProductsRequest(productIdentifiers: productIdentifiers)

        request.delegate = self

        request.start()

        

//        if SKPaymentQueue.canMakePayments(){

//            let request = SKProductsRequest(productIdentifiers: Set<AnyHashable>([type.rawValue]) as! Set<String>)

//            request.delegate = self

//            request.start()

//        }

    }

    

    func purchaseProduct(productType: ProductType) {

        /*guard let product = self.products.filter({$0.productIdentifier == productType.rawValue}).first else {

            self.delegate?.inAppLoadingFailed(error: InAppErrors.noProductsAvailable)

            return

        }

         let payment = SKMutablePayment(product: product)

         SKPaymentQueue.default().add(payment)

         */

        

        let product = self.products.filter({$0.productIdentifier == productType.rawValue}).first

       

        var payment: SKPayment? = nil

        

        if let aProduct = product {

            

            payment = SKPayment(product: aProduct)

            

        }

        

        SKPaymentQueue.default().add(self)

        

        if let aPayment = payment {

            

            SKPaymentQueue.default().add(aPayment)

            

        }

    }

    

    func restoreSubscription() {

        SKPaymentQueue.default().restoreCompletedTransactions()

        self.delegate?.inAppLoadingStarted()

    }

    

    func checkSubscriptionAvailability(_ completionHandler: @escaping (Bool) -> Void) {

        guard let receiptUrl = Bundle.main.appStoreReceiptURL,

            let receipt = try? Data(contentsOf: receiptUrl).base64EncodedString() as AnyObject else {

                completionHandler(false)

                return

        }

        

        

      /* let _ = Router.User.sendReceipt(receipt: receipt).request(baseUrl:veri).responseObject { (response: DataResponse<RTSubscriptionResponse>) in

           switch response.result {

           case .success(let value):

               guard let expirationDate = value.expirationDate,

                   let productId = value.productId else {completionHandler(false); return}

               self.expirationDate = expirationDate

               self.isTrialPurchased = value.isTrial

               self.purchasedProduct = ProductType(rawValue: productId)

               completionHandler(Date().timeIntervalSince1970 < expirationDate.timeIntervalSince1970)

           case .failure(let error):

               completionHandler(false)

           }

       }*/

        

        HitApi.shared.verifyRequestIAP(api: verifyReceiptURL,receiptURL: receiptUrl,appSecret: AppShared_Secret, showLoader: true) { (response:SubscriptionResponse) in

           

           guard let resp = response.latest_receipt_info?.first ,let pending_renewal_info = response.pending_renewal_info?.first, let expiryDate = resp.expires_date,let range = expiryDate.range(of: "Etc/GMT") ,let productId = resp.product_id,let isTrial = resp.is_trial_period else { completionHandler(false); return}

            

           // guard latestReceiptInfo else {return}

            

          /*

             guard let resp = response.latest_receipt_info as? [[String:Any]] else {return}

             let latestReceiptInfo = self.getJsonResponse( resp)

             

             if let expirationDateStringWithTimeZone = latestReceiptInfo?["expires_date"] as? String,

                let range = expirationDateStringWithTimeZone.range(of: "Etc/GMT") {

                let expirationDateString = expirationDateStringWithTimeZone.substring(to: range.lowerBound)

                let dateFormatter = DateFormatter()

                dateFormatter.dateFormat = "yyy-MM-dd HH:mm:ss"

                dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)

                self.expirationDate = dateFormatter.date(from: expirationDateString)

            }

            

            guard let isTrial = Bool(latestReceiptInfo?["is_trial_period"] as? String ?? "false")

                  ,let productId = latestReceiptInfo?["product_id"] as? String else {completionHandler(false); return}

            self.isTrialPurchased = isTrial

            self.purchasedProduct = ProductType(rawValue: productId)

            completionHandler(Date().timeIntervalSince1970 < self.expirationDate!.timeIntervalSince1970)*/

           /* let expirationDateString = expiryDate.substring(to: range.lowerBound)

            guard !expirationDateString.isEmpty else{

                completionHandler(false); return

            }*/

            let dateFormatter = DateFormatter()

            dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"

            dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)

            let expirationDate = dateFormatter.date(from: expiryDate)

            

            self.expirationDate = expirationDate

            self.isTrialPurchased = Bool(isTrial)

            self.purchasedProduct = ProductType(rawValue: productId)

            let renew_status = pending_renewal_info.auto_renew_status == "0" ? false : true

            AlreadySubscribed = renew_status

            completionHandler(renew_status)

           // completionHandler(Date().timeIntervalSince1970 < expirationDate!.timeIntervalSince1970)

            

        

        }

    }

    func getJsonResponse(_ jsonResponse: [[String:Any]]) -> [String:Any]? {

        let receiptInfo = jsonResponse

        if !receiptInfo.isEmpty{

            guard let lastReceipt = receiptInfo.first else {

                return nil

            }

            return lastReceipt

            

        }else{

            return nil

        }

    }

    func getExpirationDateFromResponse(_ jsonResponse: NSDictionary) -> Date? {

        

        if let receiptInfo: NSArray = jsonResponse["latest_receipt_info"] as? NSArray {

            

            let lastReceipt = receiptInfo.firstObject as! NSDictionary

            let formatter = DateFormatter()

            formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"

            

            if let expiresDate = lastReceipt["expires_date"] as? String {

                return formatter.date(from: expiresDate)

            }

            

            return nil

        }

        else {

            return nil

        }

    }

    

    func updateSubscriptionStatus() {

        self.checkSubscriptionAvailability({ [weak self] (isSubscribed) in

            self?.isSubscriptionAvailable = isSubscribed

        })

    }

    func refreshReceipt() {

        let request = SKReceiptRefreshRequest()

        request.delegate = self // to be able to receive the results of this request, check the SKRequestDelegate protocol

        request.start()

    }

}




struct SubscriptionResponse: Codable {

    var status: Int?

    var latest_receipt_info : [DataReciept]?

    var pending_renewal_info : [DataPendingRenew]?

}


struct DataReciept:Codable{

    var expires_date : String?

    var is_trial_period : String?

    var product_id : String?

}


struct DataPendingRenew : Codable{

    var auto_renew_product_id : String?

    var auto_renew_status:String?

}

    /*var expirationDate: Date?

    var isTrial: Bool?

    var productId: String?


    func mapping(map: Map) {

        guard let latestReceiptInfo = (map.JSON["latest_receipt_info"] as? [[String: AnyObject]])?.first else {return}

        

        if let expirationDateStringWithTimeZone = latestReceiptInfo["expires_date"] as? String,

            let range = expirationDateStringWithTimeZone.range(of: "Etc/GMT") {

            let expirationDateString = expirationDateStringWithTimeZone.substring(to: range.lowerBound)

            let dateFormatter = DateFormatter()

            dateFormatter.dateFormat = "yyy-MM-dd HH:mm:ss"

            dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)

            self.expirationDate = dateFormatter.date(from: expirationDateString)

        }

        self.isTrial = Bool(latestReceiptInfo["is_trial_period"] as? String ?? "false")

        self.productId = latestReceiptInfo["product_id"] as? String

    }*/



extension InAppManager: SKPaymentTransactionObserver {

    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {

        for transaction in transactions {

            guard let productType = ProductType(rawValue: transaction.payment.productIdentifier) else {fatalError()}

            switch transaction.transactionState {

            case .purchasing:

                self.delegate?.inAppLoadingStarted()

            case .purchased:

                SKPaymentQueue.default().finishTransaction(transaction)

                self.updateSubscriptionStatus()

                self.isSubscriptionAvailable = true

                self.delegate?.inAppLoadingSucceded(productType: productType)

                self.delegate?.inAppPurchased(value: true)

            case .failed:

                if let transactionError = transaction.error as NSError?,

                    transactionError.code != SKError.paymentCancelled.rawValue {

                    self.delegate?.inAppLoadingFailed(error: transaction.error)

                } else {

                    self.delegate?.inAppLoadingFailed(error: InAppErrors.noSubscriptionPurchased)

                }

                SKPaymentQueue.default().finishTransaction(transaction)

            case .restored:

                SKPaymentQueue.default().finishTransaction(transaction)

                self.updateSubscriptionStatus()

                self.isSubscriptionAvailable = true

                self.delegate?.inAppLoadingSucceded(productType: productType)

            case .deferred:

                self.delegate?.inAppLoadingSucceded(productType: productType)

            @unknown default:

                fatalError()

            }

        }

    }

    

    func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Swift.Error) {

        self.delegate?.inAppLoadingFailed(error: error)

    }

    

}


//MARK: - SKProducatsRequestDelegate

extension InAppManager: SKProductsRequestDelegate {

    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {

        guard response.products.count > 0 else {return}

        self.products = response.products

        guard let id = paymentProductType else{return}

        self.purchaseProduct(productType: id)

    }

}



//////////////////////////////////////////

//MARK:- Helper Class



import SystemConfiguration

class HitApi {

    

    private init() {}

    static let shared = HitApi()

    func verifyRequestIAP<T: Decodable>(api: String, receiptURL:URL,appSecret:String, showLoader:Bool = true, outputBlock: @escaping (T) -> () ) {

        

        print("hitting:" , api)

      

        if !isConnectedToNetwork(){

            Utility().displayAlert(title: "", message: "You are not connected with internet", control: ["Ok"])

            return

        }

    

        let receiptString = try? Data(contentsOf:receiptURL).base64EncodedString()

        let requestData : [String : Any] = ["receipt-data" : receiptString,

                                                  "password" : appSecret]

        guard let url = URL(string: api) else {return}

        var request = URLRequest(url: url)

        

        let httpBody = try? JSONSerialization.data(withJSONObject: requestData, options: [])

    

        request.httpMethod = "POST"

        request.setValue("Application/json", forHTTPHeaderField: "Content-Type")

        request.httpBody = httpBody

        

        if showLoader {

            Utility().show_loader()

        }

        URLSession.shared.dataTask(with: request) { (data, response, error) in

            

           // Utility().hide_loader()

            

            DispatchQueue.main.async {

                Utility().hide_loader()

                

                if let err = error {

                    Utility().showAlert(mesg: err.localizedDescription)

                    return

                }

                

                guard let data = data else {

                    Utility().showAlert(mesg: "Getting Data nil from Server")

                    return

                }

                

                let abc = try? JSONSerialization.jsonObject(with: data, options: [])

                print(abc as Any)

                

                do {

                    let obj = try JSONDecoder().decode(T.self, from: data)

                    outputBlock(obj)

                } catch let jsonErr {

                    Utility().showAlert(mesg: jsonErr.localizedDescription)

                }

            }

        }.resume()

    }

    

    //MARK: - Check Internet

    private func isConnectedToNetwork() -> Bool {

        var zeroAddress = sockaddr_in()

        zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))

        zeroAddress.sin_family = sa_family_t(AF_INET)

        let defaultRouteReachability = withUnsafePointer(to: &zeroAddress) {

            $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {zeroSockAddress in

                SCNetworkReachabilityCreateWithAddress(nil, zeroSockAddress)

            }

        }

        if(defaultRouteReachability == nil){

            return false

        }

        var flags : SCNetworkReachabilityFlags = []

        if !SCNetworkReachabilityGetFlags(defaultRouteReachability!, &flags) {

            return false

        }

        

        let isReachable = flags.contains(.reachable)

        let needsConnection = flags.contains(.connectionRequired)

        

        return (isReachable && !needsConnection)

    }

    

    private func createBody(parameters: [String: Any],

                            video:[String:Data],

                            document:[String:Data],

                            boundary: String,

                            extensionDocument:String,

                            mimeType: String) -> Data {

        var body = Data()

        

        let boundaryPrefix = "--\(boundary)\r\n"

        

        

        

        for case let (key, value as String) in parameters{

            body.append(boundaryPrefix)

            body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")

            body.append("\(value)\r\n")

        }

        

        for case let (key, value as Int) in parameters{

            body.append(boundaryPrefix)

            body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")

            body.append("\(value)\r\n")

        }

        

        for case let (key, value as UIImage) in parameters {

            

            body.append(boundaryPrefix)

            body.append("Content-Disposition: form-data; name=\"\(key)\"; filename=\"\(UUID().uuidString).jpg\"\r\n")

            body.append("Content-Type: \(mimeType)\r\n\r\n")

            body.append(value.compressImage())

            body.append("\r\n")

            body.append("--".appending(boundary.appending("--\r\n")))

        }

        

        for case let (key, value as [UIImage]) in parameters {

            for (ind,image) in value.enumerated()

            {

            body.append(boundaryPrefix)

            body.append("Content-Disposition: form-data; name=\"\(key)[\(ind)]\"; filename=\"\(UUID().uuidString).jpg\"\r\n")

            body.append("Content-Type: \(mimeType)\r\n\r\n")

            body.append(image.compressImage())

            body.append("\r\n")

            body.append("--".appending(boundary.appending("--\r\n")))

            }

            }

        

        

        

        for case let (key, value) in video {

            

            body.append(boundaryPrefix)

            body.append("Content-Disposition: form-data; name=\"\(key)\"; filename=\"\(UUID().uuidString).mp4\"\r\n")

            body.append("Content-Type: application/octet-stream\r\n\r\n")

            body.append(value)

            body.append("\r\n\r\n")

            body.append("--".appending(boundary.appending("--")))

        }

        

        for case let (key, value) in document {

            body.append(boundaryPrefix)

            body.append("Content-Disposition: form-data; name=\"\(key)\"; filename=\"\(UUID().uuidString).\(extensionDocument)\"\r\n")

            body.append("Content-Type: text/csv\r\n\r\n")

            body.append(value)

            body.append("\r\n\r\n")

            body.append("--".appending(boundary.appending("--")))

        }

        

        return body

    }

}



extension Data {

    mutating func append(_ string: String, using encoding: String.Encoding = .utf8) {

        if let data = string.data(using: encoding) {

            append(data)

        }

    }

}



import SKActivityIndicatorView

class Utility: NSObject {

    

    let topController = UIApplication.topViewController()

    

    func show_loader(userInteraction:Bool = false) {

          

          SKActivityIndicator.spinnerColor(UIColor.darkGray)

          SKActivityIndicator.statusTextColor(UIColor.black)

          let myFont = UIFont(name: "AvenirNext-DemiBold", size: 18)

          SKActivityIndicator.statusLabelFont(myFont!)

          SKActivityIndicator.spinnerStyle(.spinningFadeCircle)

          SKActivityIndicator.show("Loading...", userInteractionStatus: userInteraction)

      }

       func hide_loader() {

          

         SKActivityIndicator.dismiss()

      }

    func showAlert(mesg:String) {

        displayAlert(message: mesg.uppercased(), control: ["Ok"])

    }

    //MARK:- Display alert without completion

    

    func displayAlert(title:String = "" , message:String, control:[String]){

        

        let alertController = UIAlertController(title: title, message: message.uppercased() , preferredStyle: .alert)

        

        for str in control{

            

            let alertAction = UIAlertAction(title: str, style: .default, handler: nil)

            

            alertController.addAction(alertAction)

        }

        

        topController?.present(alertController, animated: true, completion: nil)

        

    }

}


extension UIApplication {

    class func topViewController(controller: UIViewController? = (UIApplication.shared.delegate as? AppDelegate)?.window?.rootViewController) -> UIViewController? {

        

        if let navigationController = controller as? UINavigationController {

            return topViewController(controller: navigationController.visibleViewController)

        }

        if let tabController = controller as? UITabBarController {

            if let selected = tabController.selectedViewController {

                return topViewController(controller: selected)

            }

        }

        if let presented = controller?.presentedViewController {

            return topViewController(controller: presented)

        }

        return controller

    }

}



//////////////////////////

// Subscription Class for ViewController 


//

//  SubscriptionVC.swift



import UIKit


class SubscriptionVC: UIViewController, InAppManagerDelegate {

   

    

    func inAppLoadingStarted() {

        

    }

    

    func inAppLoadingSucceded(productType: ProductType) {

        

    }

    func inAppPurchased(value: Bool) {

        dismissing?(true)

        dismiss(animated: true, completion: nil)

    }

    func inAppLoadingFailed(error: Error?) {

        Utility().displayAlert(message: error?.localizedDescription ?? error.debugDescription, control: ["OK"])

//        showTopAlert(error.debugDescription)

    }

    

    func subscriptionStatusUpdated(value: Bool) {

        

    }

    

    


    @IBOutlet weak var textViewDetail: UITextView!

    var dismissing : ((Bool)->Void)?


    override func viewDidLoad() {

        super.viewDidLoad()

       

        

        let text = "Payment will be charged to iTunes Account at confirmation of purchase.\nSubscription automatically renews unless auto-renew is turned off at least 24-hours before the end of the current period.\nAccount will be charged for renewal within 24-hours prior to the end of the current period, and identity thecost of the renewal.\nSubscription may be managed by user and auto-renewal may be turned off by going to the user's Account settings after purchase.\n Any unused portion of the free trial period, if offered, will be forfieted when the user purchases asubscription to that publication, where applicable Length of Subscription: 1 Week\nPrice: Free for 3 days, then $3.99/Week after free trial"

        textViewDetail.text = text

        InAppManager.shared.delegate = self

    }

   

    

    @IBAction func btnCross(_ sender: Any) {

        self.dismiss(animated: true, completion: nil)

    }

    

    @IBOutlet weak var btnSubscribe: UIButton!

    @IBAction func btnSubscribe(_ sender: Any) {

       

        InAppManager.shared.loadProducts(type:.yearly)

    }

    

    @IBOutlet weak var btn3DayFreeTrial: UIButton!

    @IBAction func btn3DayFreeTrial(_ sender: Any) {

        InAppManager.shared.loadProducts(type:.weekly)


    }

    

    

    @IBAction func btnPrivacyPolicy(_ sender: Any) {

    }

    @IBAction func btnTermAction(_ sender: Any) {

    }

    

    /*

    // MARK: - Navigation


    // In a storyboard-based application, you will often want to do a little preparation before navigation

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        // Get the new view controller using segue.destination.

        // Pass the selected object to the new view controller.

    }

    */


}


            

//////

No comments:

Post a Comment