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
}
}
//
// 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.
}
*/
}
//////