iOS - IAP总结

2021-06-09  本文已影响0人  lieon

IAP的实现的流程

IAP流程

IAP自动续期订阅代码实战

IAPService的对外接口

func buyProduct(_ productId: String) {
        if SKPaymentQueue.canMakePayments() {
            let prefetched = prefetchProducts[productId]
            let lastTansation = SKPaymentQueue.default().transactions.filter { $0.payment.productIdentifier == productId }.first
            if let lastTansation = lastTansation, lastTansation.transactionState == .purchased { /// 购买成功过,直接去校验
                let productId = lastTansation.payment.productIdentifier
                let isFirstBuy = lastTansation.original == nil
                verifyBuySession(productId, isFirstBuy: isFirstBuy) {
                    SKPaymentQueue.default().finishTransaction(lastTansation)
                }
            } else if let prefetched = prefetched {
                let payment = SKPayment(product: prefetched)
                SKPaymentQueue.default().add(payment)
            } else {
                var products = Set<String>()
                products.insert(productId)
                let request = SKProductsRequest(productIdentifiers: products)
                request.delegate = self
                request.start()
            }
        } else {
            buyFailure.call(nil)
            NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
        }
    }

 func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        if response.products.isEmpty {
            return
        }
        if request == preFetchRequest {
            /// 注意线程安全
            lock.lock()
            response.products.forEach { product in
                prefetchProducts[product.productIdentifier] = product
            }
            lock.unlock()
            return
        }
        
        if let product = response.products.first {
            let payment = SKPayment(product: product)
            SKPaymentQueue.default().add(payment)
        } else {
            buyFailure.call(nil)
            NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
        }
    }

 func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        transactions.forEach { (transation) in
            switch transation.transactionState {
            /// 购买成功
            case .purchased:
                let productId = transation.payment.productIdentifier
                let isFirstBuy = transation.original == nil
                verifyBuySession(productId, isFirstBuy: isFirstBuy) {
                    SKPaymentQueue.default().finishTransaction(transation)
                }
            case .failed:
                handleFailed(transation)
            case .restored:
                handleRestored(transation)
            case .purchasing:
                break
            case .deferred:
                break
            @unknown default:
                debugPrint("IAPService - @unknown default")
            }
        }
    }
    /// 冷启动校验未完成的订单
    func wakeUp() {
        guard SKPaymentQueue.canMakePayments() else {
            return
        }
        guard let lastTansition = SKPaymentQueue.default().transactions.filter({ $0.transactionState == .purchased}).first  else {
            return
        }
        let productId = lastTansition.payment.productIdentifier
        let isFirstBuy = lastTansition.original == nil
        verifyBuySession(productId, isFirstBuy: isFirstBuy) {
            SKPaymentQueue.default().finishTransaction(lastTansition)
        }
    }
 func restore() {
        guard SKPaymentQueue.canMakePayments() else {
            return
        }
        SKPaymentQueue.default().restoreCompletedTransactions()
    }

 func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
        /// 凭证无效则刷新一下
        if reciepString == nil {
            let refresh = SKReceiptRefreshRequest()
            refresh.delegate = self
            refresh.start()
        } else {
            verifyRestoreSession()
        }
    }
    
  func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
        restoreFailure.call(error)
        NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
    }

 func requestDidFinish(_ request: SKRequest) {
        /// 如果是恢复购买时刷新凭证的request
        if request is SKReceiptRefreshRequest {
            if reciepString == nil {
                restoreFailure.call(AppError(message: "error.SKReceiptRefreshRequest", code: ErrorCode(rawValue: -100) ?? .none))
                NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
            } else {
                verifyRestoreSession()
            }
        }
        if request == preFetchRequest {
            preFetchRequest = nil
        }
    }
  func prefetchProducts(_ productIds: Set<String>) {
        guard SKPaymentQueue.canMakePayments() else { return }
        preFetchRequest = SKProductsRequest(productIdentifiers: productIds)
        preFetchRequest?.delegate = self
        preFetchRequest?.start()
    }

内部接口

 private override init() {
        super.init()
        SKPaymentQueue.default().add(self)
    }

extension IAPService: SKProductsRequestDelegate {
    func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        if response.products.isEmpty {
            return
        }
        if request == preFetchRequest {
            /// 注意线程安全
            lock.lock()
            response.products.forEach { product in
                prefetchProducts[product.productIdentifier] = product
            }
            lock.unlock()
            return
        }
        
        if let product = response.products.first {
            let payment = SKPayment(product: product)
            SKPaymentQueue.default().add(payment)
        } else {
            buyFailure.call(nil)
            NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
        }
    }
    
    func request(_ request: SKRequest, didFailWithError error: Error) {
        buyFailure.call(error)
        NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
    }
    
    func requestDidFinish(_ request: SKRequest) {
        /// 如果是恢复购买时刷新凭证的request
        if request is SKReceiptRefreshRequest {
            if reciepString == nil {
                restoreFailure.call(AppError(message: "error.SKReceiptRefreshRequest", code: ErrorCode(rawValue: -100) ?? .none))
                NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
            } else {
                verifyRestoreSession()
            }
        }
        if request == preFetchRequest {
            preFetchRequest = nil
        }
    }
}

extension IAPService: SKPaymentTransactionObserver {
    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        transactions.forEach { (transation) in
            switch transation.transactionState {
            /// 购买成功
            case .purchased:
                let productId = transation.payment.productIdentifier
                let isFirstBuy = transation.original == nil
                verifyBuySession(productId, isFirstBuy: isFirstBuy) {
                    SKPaymentQueue.default().finishTransaction(transation)
                }
            case .failed:
                handleFailed(transation)
            case .restored:
                handleRestored(transation)
            case .purchasing:
                break
            case .deferred:
                break
            @unknown default:
                debugPrint("IAPService - @unknown default")
            }
        }
    }
    
    func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
        /// 凭证无效则刷新一下
        if reciepString == nil {
            let refresh = SKReceiptRefreshRequest()
            refresh.delegate = self
            refresh.start()
        } else {
            verifyRestoreSession()
        }
    }
    
    func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
        restoreFailure.call(error)
        NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
    }
}
  /// 校验购买时使用
    fileprivate func verifyBuySession(_ productId: String? = nil, isFirstBuy: Bool, success: (() -> Void)? = nil) {
        let provider = MoyaProvider<IAPNetWorkTarget>()
        guard let reciepString = reciepString else {
            return
        }
        
        func paraseReponse(_ response: Result<Response, MoyaError>, success: (() -> Void)? = nil) {
            switch response {
            case .success(let res):
                guard let resModel = try? res.model(IAPResponse.self) else {
                    return
                }
                if let data = resModel.data {
                    self.buySuccess.call(data)
                    NotificationCenter.default.post(name: NSNotification.Name.iapBuySuccess, object: data)
                    success?()
                } else {
                    let error = AppError(message: resModel.message ?? "", code: ErrorCode(rawValue: resModel.status) ?? .none)
                    self.buyFailure.call(error)
                    NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
                }
            case .failure(let error):
                self.buyFailure.call(AppError(message: error.localizedDescription, code: ErrorCode(rawValue: -100) ?? .none))
                NotificationCenter.default.post(name: NSNotification.Name.iapBuyFailure, object: nil)
            }
        }
        if isFirstBuy, let productId = productId { /// 订阅
            provider.request(.purchase(productId, reciepString)) { response in
                paraseReponse(response, success: success)
            }
        } else { /// 续费
            provider.request(.checkReceipt(reciepString)) { response in
                paraseReponse(response, success: success)
            }
        }
    }
    
    /// 校验恢复时使用
    fileprivate func verifyRestoreSession(_ success: (() -> Void)? = nil) {
        let provider = MoyaProvider<IAPNetWorkTarget>()
        guard let reciepString = reciepString else {
            return
        }
        provider.request(.checkReceipt(reciepString)) { response in
            switch response {
            case .success(let res):
                guard let resModel = try? res.model(IAPResponse.self) else {
                    return
                }
                if let data = resModel.data {
                    self.restoreSuccess.call(data)
                    NotificationCenter.default.post(name: NSNotification.Name.iapRestoreSuccess, object: data)
                    success?()
                } else {
                    let error = AppError(message: resModel.message ?? "", code: ErrorCode(rawValue: resModel.status) ?? .none)
                    self.restoreFailure.call(error)
                    NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
                }
            case .failure(let error):
                self.restoreFailure.call(AppError(message: error.localizedDescription, code: ErrorCode(rawValue: -100) ?? .none))
                NotificationCenter.default.post(name: NSNotification.Name.iapRestoreFailure, object: nil)
            }
        }
    }
    
    ///获取当前凭证的最新信息:比如订阅过期,Apple知道订阅过期了,但是app端有可能不知道,所以需要主动获取一次
    fileprivate func checkCurrentReciept() {
        let provider = MoyaProvider<IAPNetWorkTarget>()
        guard let reciepString = reciepString else {
            return
        }
        provider.request(.checkReceipt(reciepString)) { response in
            switch response {
            case .success(let res):
                guard let resModel = try? res.model(IAPResponse.self) else {
                    return
                }
                if let data = resModel.data {
                    NotificationCenter.default.post(name: NSNotification.Name.iapInfoUpdated, object: data)
                }
            case .failure:
                break
            }
        }
    }

如何防止凭证被伪造?(面试经常问)

上一篇 下一篇

猜你喜欢

热点阅读