前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >StoreKit2 有这么香?嗯,我试过了,真香

StoreKit2 有这么香?嗯,我试过了,真香

作者头像
HelloWorld杰少
发布2022-08-04 14:19:05
2.9K0
发布2022-08-04 14:19:05
举报
文章被收录于专栏:HelloWorld杰少HelloWorld杰少

前文

PurchaseX 迎来首次新的更新啦!此次更新引入了 Apple 新推出的 StoreKit2 框架。

想必开发过 In-App-Purchase 的同学肯定都应该体会过被他生涩难懂的 API,复杂的消息回调,不合理的数据结构以及莫名其妙的丢单等问题折磨过,于是 Apple 针对 StoreKit 做了一次全面的升级,推出了 Storekit2 框架。

在阅读下面内容之前,我先将一些在下面的文章中会涉及到的 Swift 语言的新特性和大家做一下说明:

  1. @aync/@await: Swift5.5 新推出的多线程编程 API
  2. @Actor: 防止应用在多线程中造成数据竞争,是保证多线程安全性的新类型
  3. JWS: 全文是 JSON Web Signature,是一套加密校验体系,在 StoreKit2 中通过此校验体系来校验订单

接下来,就让我带领大家来看下,StoreKit2 相比 StoreKit 有哪些重大的变化吧!

请求商品

在 StoreKit2 中,请求商品的 API 变得简洁无比,配合上使用 @aync/@await,只要简简单单的一行代码,即可从 AppStore 获得内购商品。代码如下:

代码语言:javascript
复制
@MainActor public func requestProductsFromAppstore(productIds: [String]) async -> [Product]? {
    products = try? await Product.products(for: Set.init(productIds))
    return products
}

再来看下旧版本内购是如何获取商品信息的,代码如下:

代码语言:javascript
复制
    // MARK: - requestProductsFromAppstore
    /// - Request products form appstore
    /// - Parameter completion: a closure that will be called when the results returned from the appstore
    public func requestProductsFromAppstore(productIds: [String]?, completion: @escaping (_ notification: PurchaseXNotification?) -> Void) {
        // save request products info
        requestProductsCompletion = completion
        
        guard productIds != nil || productIds!.count > 0 else {
            PXLog.event(.productIdArrayEmpty)
            DispatchQueue.main.async {
                completion(.productIdArrayEmpty)
            }
            return
        }
        
        if products != nil {
            products?.removeAll()
        }
                
        configuredProductIdentifiers = Set(productIds!)
        
        // 1. Cancel pending requests
        productsRequest?.cancel()
        // 2. Init SKProductsRequest
        productsRequest = SKProductsRequest(productIdentifiers: configuredProductIdentifiers!)
        // 3. Set Delegate to receive the notification
        productsRequest!.delegate = self
        // 4. Start request
        productsRequest!.start()
    }

对比完代码后,你就可以看出使用 StoreKit2 得有多方便了。

首先,利用 @aync/@await 新特性,我们的代码可以像同步执行一样获取商品信息了,再也不用因为获取商品是异步执行的方式,而去写那些地狱级的闭包嵌套了;StoreKit2 里面商品对象已经由原来的 SKProduct 变化为 Product,请求商品也只需要仅仅一行代码即可,简单易懂。

其次,利用 StoreKit2,我们可以根据 Product 对象里的 type 类型,来获取返回的商品中的商品类型,代码如下:

代码语言:javascript
复制
    /// Array of consumable products
    public var consumableProducts: [Product]? {
        guard products != nil else {
            return nil
        }
        
        return products?.filter({ product in
            product.type == .consumable
        })
    }
    
    /// Array of nonConsumbale products
    public var nonConsumbaleProducts: [Product]? {
        guard products != nil else {
            return nil
        }
        
        return products?.filter({ product in
            product.type == .nonConsumable
        })
    }
    
    /// Array of subscriptio products
    public var subscriptionProducts: [Product]? {
        guard products != nil else {
            return nil
        }
        
        return products?.filter({ product in
            product.type == .autoRenewable
        })
    }
    
    /// Array of nonSubscription products
    public var nonSubscriptionProducts: [Product]? {
        guard products != nil else {
            return nil
        }
        
        return products?.filter({ product in
            product.type == .nonRenewable
        })
    }

在老的内购里面,我们是无法通过 SKProduct 对象来分辨商品类型的,这一步只能由我们开发者自行去判断了,现在 Apple 在 StoreKit2 中已经帮我们做好了。

发起支付

接下来,再来说一下支付功能。

不得不说 StoreKit2 提供的新的 API 都非常的精简,都只需要一句代码就可以完成功能,代码如下:

代码语言:javascript
复制
let result = try await product.purchase()

是不是非常的简单,在 StoreKit2 中已经不再需要用到 SKPaymentTransactionObserver 代理了。上述代码它的返回值 result 是 Product.PurchaseResult 类型,它是一个枚举类型,定义了此次购买的订单状态,分别为:

代码语言:javascript
复制
public enum PurchaseResult {

    /// The purchase succeeded with a `Transaction`.
    case success(VerificationResult<Transaction>)

    /// The user cancelled the purchase.
    case userCancelled

    /// The purchase is pending some user action.
    ///
    /// These purchases may succeed in the future, and the resulting `Transaction` will be
    /// delivered via `Transaction.updates`
    case pending
}

success 表明此次购买成功,userCancelled 表明用户取消了此次购买,pending 表明此次购买被挂起。

我们可以通过 switch 条件 语句,来分别处理这些状态,代码如下:

代码语言:javascript
复制
switch result {
        case .success(let verificationResult):
            let checkResult = checkTransactionVerificationResult(result: verificationResult)
            if !checkResult.verified {
                purchaseState = .failedVerification
            }
            
            let validatedTransaction = checkResult.transaction
            
            await validatedTransaction.finish()
                        
        case .userCancelled:
            purchaseState = .cancelled
            
        case .pending:
            purchaseState = .pending
            
        default:
            purchaseState = .unknown
        }

购买成功也就是状态为 success 的时候,该枚举还返回了一个 VerificationResult类型参数,这个参数是用来干嘛的呢!别急,这部分内容我放在了下面的内容中做说明。最终完成购买,我们还需要进行最后的一步结束交易,还是用一句代码来结束,那就是:

代码语言:javascript
复制
await validatedTransaction.finish()

validatedTransaction 是一个 Transaction 类型,由枚举参数返回。

验证票据

看到这里,有的同学可能会问,在上一版本的内购中,我们需要对购买的商品订单 进行票据验证,而且验证的过程还非常的麻烦,但是在新版本中怎么没有体现出来呢!难道 Apple 已经默默地帮你完成了?

说的没错,在上一版本的内购中,苹果提供了俩种验证方式给开发者对票据进行验证,分别是本地验证和远程验证。想必看过我 PurchaseX 第一版本的同学都应该清楚本地验证有多麻烦,我们要借用第三方的 OpenSSL 库去解析票据的各种属性和值,然后去一一验证,在这里我就不多做阐述了,感兴趣的可以去看下我的代码。

在新版本中,苹果引入了 JWS 来帮助我们校验订单的安全性,发起支付后,purchase() 函数会返回给我们一个枚举类型 PurchaseResult,并且当枚举值为 success 的时候,我们即可通过它的回调参数 VerificationResult 来判断当前的订单是 verified 还是 unverified。VerificationResult 这个类型其实也是个枚举类型,代码如下:

代码语言:javascript
复制
@frozen public enum VerificationResult<SignedType> {

    /// The associated value failed verification for the provided reason.
    case unverified(SignedType, VerificationResult<SignedType>.VerificationError)

    /// The associated value passed all automatic verification checks.
    case verified(SignedType)

    ...
}

当你拿到 PurchaseResult 的时候,苹果就已经默默的帮我们在背后完成了 JWS 校验。

在新版本中,发起购买的完成代码如下:

代码语言:javascript
复制
    // MARK: - purchase
    /// Start the process to purchase a product.
    /// - Parameter product: Product object
    public func purchase(product: Product) async throws -> (transaction: Transaction?, purchaseState: PurchaseXState){
        guard purchaseState != .inProgress else {
            throw PurchaseXException.purchaseInProgressException
        }
        
        purchaseState = .inProgress
        
        // Start a purchase transaction
        guard let result = try? await product.purchase() else {
            purchaseState = .failed
            throw PurchaseXException.purchaseException
        }
        
        switch result {
        case .success(let verificationResult):
            let checkResult = checkTransactionVerificationResult(result: verificationResult)
            if !checkResult.verified {
                purchaseState = .failedVerification
                throw PurchaseXException.transactionVerificationFailed
            }
            
            let validatedTransaction = checkResult.transaction
            
            await validatedTransaction.finish()
            
            // Because consumable's transaction are not stored in the receipt, So treat it differently.
            if validatedTransaction.productType == .consumable {
                if !PXDataPersistence.purchase(productId: product.id){
                    PXLog.event(.consumableKeychainError)
                }
            }
            purchaseState = .complete
            return (transaction: validatedTransaction, purchaseState: .complete)
        case .userCancelled:
            purchaseState = .cancelled
            return (transaction: nil, purchaseState: .cancelled)
        case .pending:
            purchaseState = .pending
            return (transaction: nil, purchaseState: .pending)
        default:
            purchaseState = .unknown
            return (transaction: nil, purchaseState: .unknown)
        }
    }

其他

在上一个版本的内购中,如果你的应用包含了非消耗品,那么开发者就需要为此提供一个“恢复购买”的按钮,来保证用户在新设备上能同步这些非消耗品。

但是在 StoreKit2 中,就不再需要这个恢复按钮了,因为在 StoreKit2 中, 我们可以直接获取所有已经购买过的非消耗品和订阅类商品的记录,只需要简单的通过调用

代码语言:javascript
复制
Transaction.currentEntitlements

即可获取。但是该 API 返回的数据并不包括消耗品的购买记录,所以如果想统计消耗品的购买记录,需要开发者单独的统计。

其次,在上一版本中,我们若想去管理订阅类的商品,需要去系统的设置中查看,但是该步骤个人觉得内嵌的太深,相信现在还是有很多人不清楚该如何去手动关闭订阅。但是在 StoreKit2 中,它直接提供了一个 API 可以在应用内弹出管理订阅类商品的界面,也仅需一行代码:

代码语言:javascript
复制
try await AppStore.showManageSubscriptions(in: scene)

如图所示:

image

很方便吧!

最后,StoreKit2 还提供了为内购商品退款的 API,原先退款的方式需要玩家在苹果官方网站上登录自己的 AppleID 来申请退款,非常的不方便;现在可以直接在应用中进行退款操作,开发者只需要调用下方的 API 就可以在应用中弹出退款界面,相当的人性化:

代码语言:javascript
复制
@inlinable public func beginRefundRequest(in scene: UIWindowScene) async throws -> Transaction.RefundRequestStatus

如图所示:

image

结尾

经过上述的一番对比,可以发现 StoreKit2 相比于之前的版本,已经发生了翻天覆地的变化,它的 API 简洁直观,配合使用 @aync/@await 这一新特性,使得它的内购代码阅读起来更加的简单,非常容易上手。说了几个它的优势,再来说说它唯一的一个硬伤吧!那就是 StoreKit2 目前只支持 iOS15。对于需要支持 iOS15 以下的机器,还得使用原先的那一套内购逻辑。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-10-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 HelloWorld杰少 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前文
  • 请求商品
  • 发起支付
  • 验证票据
  • 其他
  • 结尾
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档