95d71a30f2e82953ac0828ea5e365bdf
StoreKit 的新改变

概述

本文主要介绍 iOS 10.3 以来,以及 iOS 11 版本中 StoreKit 相关的,全文分为三个主要部分,分别介绍:

  • 应用内购买(IAP)的实现流程
  • 应用内购买(IAP)在 App Store 中推广
  • 用户打分、评论、开发者回复等机制的改变

背景

对于在 App Store 中上架的应用来说,通常应用内购买(In-app purchase,简称 IAP) 在总利润中占据了相当大的比例。所以能够使用 IAP ,把内容卖给用户并在此过程中提供良好的用户体验对开发者或者公司而言就显得非常重要。整个 IAP 的过程,在客户端的实现依赖于 StoreKit 这个框架,因此这一节主要讲述的是苹果在 iOS 11 系统中对 StoreKit 框架 作出的改进。

不管读者有没有使用 StoreKit 的经验,在介绍 StoreKit 的新特性之前,我们先来复习一下 IAP 的整个流程。首先,IAP 主要用来销售电子的内容或者服务。至于实物交易,则应该通过 Apple Pay(或者国内的各种支付工具)来完成。

其次,IAP 可以分为四种类型:

  1. 可消耗的产品: 比如金币
  2. 不可消耗的产品: 比如解锁应用的高级功能
  3. 不可自动续期的订阅
  4. 可自动续期的订阅

订阅是一种新的 App 购买方式,主要是为了解决开发者缺少持续收入,只能一次性卖软件的问题。如果用户通过订阅的方式购买软件,那么在订阅期结束以后,需要继续订阅才能继续使用 App。因此第三种和第四种 IAP 的区别主要就在于订阅期结束以后,用户是否会自动续订。目前默认的选择是自动续订。

IAP 的流程

这一节 Session 主要讨论的还是前两种 IAP 的方式。如果开发者想要提供 IAP 的功能,他们首先要要在应用内加载 In-app Identifier,下面是完整的流程:

  1. 加载 In-app Identifier
  2. 客户端从 App Store 中获取本地化的商品信息。
  3. 把 IAP 的购买界面展示给用户,用户可以同意购买并点击购买按钮。
  4. 用户授权购买,客户端向服务器发送购买请求。
  5. 服务器处理购买请求,并把结果返回给 StoreKit。
  6. 如果购买请求验证通过,客户端此时解锁内容或者提供金币。
  7. 至此,整个交易流程结束。

加载 In-app Identifier

In-app Identifier 为每个可销售的商品提供一个唯一标示。在客户端上,我们可以直接写死代码:

let identifiers = ["com.myCompany.myApp.product1", "com.myCompany.myApp.product2"]

也可以从自己的服务器动态的获取:

let identifiers = remoteIdentifiers()

获取商品信息

加载本地化的商品信息非常简单,只要把 identifier 传递到 SKProductsRequest 这个类的初始化方法中即可:

let request = SKProductsRequest(productIdentifiers: identifierSet)
request.delegate = self
request.start()

设置好代理后就可以发送请求了,如果请求成功,会在 didReceive 这个回调函数中收到一个商品数组,这个数组中的每一个元素和请求中的每一个 identifier 对应:

func productRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
    for product in response.products {
        // 本地化的商品名称和描述信息
        product.localizedTitle
        product.localizedDescription
        // 价格和国家
        product.price
        product.priceLocale
        // 内容的大小和版本
        product.downloadContentLengths
        product.downloadContentVersion
    }
}

需要强调的是,开发者不要缓存这些网络请求的结果,而是每次都获取实时的、最新的数据,因为用户可能会切换 App Store 的地区而且货币的汇率可能会实时变化。

展示购买界面

购买页面由各个应用自己负责绘制。但这个页面应该经过精心设计,因为不同的页面美观程度不一样,这会对用户是否购买产生较大的影响。感兴趣的读者可以参考苹果开发者网站的这个链接,里面介绍了一些能够帮助提高销量的技巧。

在展示 UI 时,我们可能涉及到货币单位的处理,比如是用人民币计价还是用美元,我们可以用这样的代码来完成:

let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = product.priceLocale
let formattedString = formatter.string(from: product.price)

需要强调的是,这里我们只要使用从 App Store 拿到的价格和国家就可以了,剩下的工作都应该交给 StoreKit 完成。尤其是这里的国家,如果不设置,默认会使用设备地区。但如果用户选择的地点是美国,而 App Store 则登录了中国商店,这里默认就会显示美元为单位。但显然应该以 App Store 商店所在的国家为准。

切记,一切交给 StoreKit 处理,开发者只要拿着被格式化过的字符串就可以了,比如汇率计算之类的也都由 StoreKit 处理。

发送购买请求

这一步也很简单, 两行代码就可以搞定。只需要把之前拿到的商品对象传到 SKPayment 的初始化方法中,构造一个 SKPayment 实例,再把这个实例加入到购买队列中即可:

let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)

此时应用内会弹出苹果设计的购买窗口,用户只要使用指纹即可同意付款。在 iOS 上,苹果重新设计了购买页面,看起来更加美观。

苹果设计了一套强大的反作弊机制来检测可疑的交易,苹果希望在交易发生之前就把它们拦截下来,而不是在交易发生后再退款。举个例子,假设有三个不同的用户(Apple ID) 购买同样的一份商品,这个场景非常普通:

但如果这个商品是游戏中的金币,而且这三个用户购买的金币对应在游戏里都是同一个账户。换句话说,三个不同的人向同一个游戏账户充值,事情就显得很可疑了。

因此,苹果希望开发者在请求支付的同时,还携带一个用户在他们账户体系中的 ID。这种 ID 可以是透明 ID,也就是说并不一定是真实的用户账户名,只要是用户名或者其他 ID 的哈希值即可。也就是说苹果并不关心开发者上传什么具体的 ID,只要这个 ID 能唯一对应一个真实用户即可。

要实现这一步非常简单,只要在原来的请求基础上新增一行代码,设置 ID 即可:

let payment = SKPayment(product: product)
payment.applicationUsername = hash(yourCustomerAccountName)
SKPaymentQueue.default().add(payment)

有了这样的账户唯一标识,苹果可以更方便的发现异常交易并提前拦截它们。

处理购买请求

当用户的购买请求经过 StoreKit 和苹果服务器的验证后,开发者可以在回调函数中接收到。这个回调函数应该尽早的注册,因为在应用程序生命周期内的任何时刻,客户端都有可能接收到回调。所以苹果推荐在 AppDelegatedidFinishLaunchingWithOptions 函数内注册:

import UIKit
import StoreKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, SKPaymentTransactionObserver {
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    SKPaymentQueue.default().add(self)
    return true
}

Demo 中的代码比较简单,直接让 self 去接受回调函数。在实际开发中,可以使用一个单独的类来处理回调:

// 处理 SKPaymentQueueObserver 事件 
// MARK: - SKPaymentTransactionObserver
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions:[SKPaymentTransaction]) {
    for transaction in transactions {
        switch transaction.transactionState {
        case .purchased:
            // Validate the purchase
        }
    }
}

通过检查 transaction 的状态,我们可以指定每种状态下的处理逻辑。如果状态显示已购买,我们还是应该和自己的服务器进行一次校验,确保交易真实有效而不是通过越狱后的某些插件完成的。被检验的,是一种被苹果称之为收据(receipt)的凭证,就像我们在超市购物或者饭店就餐后拿到的收据一样,每一个购买的商品都有自己的收据。这个收据由苹果签发,保存在客户端本地。

top Created with Sketch.