Swift实现iOS内购

ryanly

前言

  • Swift作为当前在github上成长最快的语言之一,本人在学习iOS未曾学习过OC,因此在做iOS项目过程中全部采用了Swift,下面详细介绍下Swift的内购的实现。github地址:https://github.com/RyanLeeLY/...


起步

  • 首先我就不介绍如何在iTunesConnect上添加内购商品了,总之添加完商品后,我们需要用到的是productIdentifiers即你填写的商品ID,这个ID是商品在商店中的唯一标识。

  • 导入StoreKit,创建一个类,SKPaymentTransactionObserver协议:当交易在队列中有更新或者移出队列时这个观察者会被调用;SKProductsRequestDelegate实现该协议的代理会受到商品请求返回的信息。

    public class LYIAPManager: NSObject, SKPaymentTransactionObserver, SKProductsRequestDelegate
  • 自定义协议,LYIAPRequestDelegate处理请求成功后的代理,LYIAPPaymentDelegate交易时的代理,
    LYIAPDelegate继承上面两个协议,方便使用。

@objc public protocol LYIAPDelegate: LYIAPRequestDelegate, LYIAPPaymentDelegate{
    
}

@objc public protocol LYIAPRequestDelegate: NSObjectProtocol{
    @objc optional func requestFinished()

}

@objc public protocol LYIAPPaymentDelegate: NSObjectProtocol{
    func transactionPurchased(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
    @objc optional func transactionFailed(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
    @objc optional func transactionRestore(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
    @objc optional func transactionDeferred(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
    @objc optional func transactionPurchasing(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
    @objc optional func transactionRestoreFailedWithError(_ error:NSError)
    @objc optional func transactionRestoreFinished(_ isSuccess:Bool)
    
}

全部实现

//
//  LYIAPManager.swift
//  crater
//
//  Created by 李尧 on 2016/10/11.
//  Copyright © 2016年 secstudio. All rights reserved.
//

import UIKit
import StoreKit

private func printLog<T>(_ message:T, file:String = #file, method:String = #function, line:Int = #line){
    #if DEBUG
        print("\((file as NSString).lastPathComponent)[\(line)], \(method): \(message)")
    #endif
}

@objc public protocol LYIAPDelegate: LYIAPRequestDelegate, LYIAPPaymentDelegate{
    
}

@objc public protocol LYIAPRequestDelegate: NSObjectProtocol{
    @objc optional func requestFinished()

}

@objc public protocol LYIAPPaymentDelegate: NSObjectProtocol{
    func transactionPurchased(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
    @objc optional func transactionFailed(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
    @objc optional func transactionRestore(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
    @objc optional func transactionDeferred(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
    @objc optional func transactionPurchasing(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction)
    @objc optional func transactionRestoreFailedWithError(_ error:Error)
    @objc optional func transactionRestoreFinished(_ isSuccess:Bool)
    
}

public let LYIAP = LYIAPManager.LYIAPInstance

public class LYIAPManager: NSObject, SKPaymentTransactionObserver, SKProductsRequestDelegate {
    fileprivate let VERIFY_RECEIPT_URL = "https://buy.itunes.apple.com/verifyReceipt"
    fileprivate let ITMS_SANDBOX_VERIFY_RECEIPT_URL = "https://sandbox.itunes.apple.com/verifyReceipt"
    fileprivate var restoreSuccess = false
    
    fileprivate var productDict:NSMutableDictionary?
    
    static let LYIAPInstance = LYIAPManager()
    
    var request:SKProductsRequest?
    var observer:SKPaymentTransactionObserver?
    
    weak var delegate:LYIAPDelegate?
    weak var requestDelegate:LYIAPRequestDelegate?
    weak var paymentDelegate:LYIAPPaymentDelegate?
    
    fileprivate override init() {
        
    }
    /**
     Example: let productsIds = NSSet(array: ["com.xxx.xxx.abc"])
     */
    func setRequestWithProducts(_ productsIds: NSSet, delegate: LYIAPDelegate?) {
        request = SKProductsRequest(productIdentifiers: productsIds as! Set<String>)
        request?.delegate = self
        SKPaymentQueue.default().add(self)
        
        if(delegate != nil){
            self.delegate = delegate!
            paymentDelegate = delegate!
            requestDelegate = delegate!
        }
    }
    
    func setPaymentTransactionsDelegate(_ delegate: LYIAPPaymentDelegate){
        paymentDelegate = delegate
    }
    
    func setProductsRequestDelegate(_ delegate: LYIAPRequestDelegate){
        requestDelegate = delegate
    }
    
    func removeRequestDelegate(){
        requestDelegate = nil
    }
    
    func removeProductsDelegate(){
        paymentDelegate = nil
    }
    
    func startRequest(){
        testIsNil()
        request?.start()
    }
    
    func cancelRequest(){
        testIsNil()
        request?.cancel()
    }
    
    func startPaymentWithProductId(_ productId: String){
        //if loaded
        if(SKPaymentQueue.canMakePayments()){
            guard productDict != nil else{
                printLog("products haven't been loaded")
                return
            }
            requestPaymentWithProduct(productDict![productId] as! SKProduct)
        }else{
            printLog("IAP is not supported!")
        }
    }
    
    func restorePayment(){
        restoreSuccess = false
        SKPaymentQueue.default().restoreCompletedTransactions()
    }
    
    public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
        if (productDict == nil) {
            printLog("first load products")
            productDict = NSMutableDictionary(capacity: response.products.count)
        }
        for product in response.products  {
            printLog("product \(product.productIdentifier) loaded")
            productDict!.setObject(product, forKey: product.productIdentifier as NSCopying)
        }
        requestDelegate?.requestFinished?()
    }
    
    public func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        print("updatedTransactions")
        for transaction in transactions {
            switch transaction.transactionState{
            case .purchased:
                printLog("purchased")
                paymentDelegate?.transactionPurchased(queue, transaction: transaction)
                SKPaymentQueue.default().finishTransaction(transaction)
                break
            case .failed:
                printLog("failed")
                paymentDelegate?.transactionFailed?(queue, transaction: transaction)
                SKPaymentQueue.default().finishTransaction(transaction)
                break
            case .restored:
                printLog("restore")
                restoreSuccess = true
                paymentDelegate?.transactionRestore?(queue, transaction: transaction)
                SKPaymentQueue.default().finishTransaction(transaction)
                break
            case .purchasing:
                paymentDelegate?.transactionPurchasing?(queue, transaction: transaction)
                printLog("purchasing")
                break
            case .deferred:
                paymentDelegate?.transactionDeferred?(queue, transaction: transaction)
                printLog("deferred")
                break
            }
        }
    }
    
    public func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
        printLog("retore failed with error:\(error)")
        paymentDelegate?.transactionRestoreFailedWithError?(error)
        
    }
    public func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
        printLog("finished restore")
        paymentDelegate?.transactionRestoreFinished?(restoreSuccess)
    }
    
    private func requestPaymentWithProduct(_ product: SKProduct){
        let payment = SKPayment(product: product)
        SKPaymentQueue.default().add(payment)
    }
    
    
    
    private func testIsNil(){
        if(request == nil){
            printLog("request hasn't been init")
        }else if(request?.delegate == nil){
            printLog("request delegate hasn't been set")
        }
    }
    
    func verifyPruchase(completion:@escaping (NSDictionary?, NSError?) -> Void) {
        // 验证凭据,获取到苹果返回的交易凭据
        let receiptURL = Bundle.main.appStoreReceiptURL
        // 从沙盒中获取到购买凭据
        let receiptData = NSData(contentsOf: receiptURL!)
        #if DEBUG
            let url = NSURL(string: ITMS_SANDBOX_VERIFY_RECEIPT_URL)
        #else
            let url = NSURL(string: VERIFY_RECEIPT_URL)
        #endif
        let request = NSMutableURLRequest(url: url! as URL, cachePolicy: NSURLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval: 10.0)
        request.httpMethod = "POST"
        let encodeStr = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithLineFeed)
        let payload = NSString(string: "{\"receipt-data\" : \"" + encodeStr! + "\"}")
        let payloadData = payload.data(using: String.Encoding.utf8.rawValue)
        request.httpBody = payloadData;
        
        let session = URLSession.shared
        let semaphore = DispatchSemaphore(value: -1)
        
        let dataTask = session.dataTask(with: request as URLRequest,
            completionHandler: {(data, response, error) -> Void in
                if error != nil{
                    print("error1")
                    completion(nil,error as NSError?)
                }else{
                    if (data==nil) {
                        print("error2")
                        completion(nil,error as NSError?)
                    }
                    do{
                        let jsonResult: NSDictionary = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
                        if (jsonResult.count != 0) {
                            // 比对字典中以下信息基本上可以保证数据安全
                            // bundle_id&application_version&product_id&transaction_id
                            // 验证成功
                            let receipt = jsonResult["receipt"] as! NSDictionary
                            completion(receipt,nil)
                        }
                        print(jsonResult)
                    }catch{
                        print("error3")
                        completion(nil,nil)
                    }
                }
                
                semaphore.signal()
        }) as URLSessionTask
        dataTask.resume()
        semaphore.wait()
    }
}

使用方法

  • 新建一个ViewController,实现协议LYIAPDelegate

import UIKit
import StoreKit

class ViewController: UIViewController,LYIAPDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        let productsIds = NSSet(array: ["com.xxx.xxx.abc"])
        LYIAP.setRequestWithProducts(productsIds, delegate: self)
        LYIAP.startRequest()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    func requestFinished() {
        // Do something when products have been loaded.
    }
    
    func transactionPurchased(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction) {
        // Identifier of the product that has been purchased
        debugPrint(transaction.payment.productIdentifier)
        
        LYIAP.verifyPruchase(completion: {(receipt,error) in
            // You can verify the transaction. In this callback, you will get the receipt if the transaction is verified by the APPLE. You can compare some tranction infomation with the receipt.
            debugPrint(receipt)
        })
    }
    
    func transactionRestore(_ queue: SKPaymentQueue, transaction: SKPaymentTransaction) {
        // Identifier of the product that has been restored
        // You must add restore function to your app accroding to APPLE's provisions
        debugPrint(transaction.payment.productIdentifier)
    }
    
    func transactionRestoreFinished(_ isSuccess: Bool) {
        // It is called when restore is finished. isSuccess will be true when some products have been restored successfully.
    }

}

部分代码参考来源:SaiWu博客园

阅读 6k

Ryan's fault
只是一个经常写出bug的程序员
190 声望
18 粉丝
0 条评论
190 声望
18 粉丝
文章目录
宣传栏