WWDC 2018:IAP最佳实践并增强活动营销功能

4,756 阅读14分钟

Session: WWDC2018 Best Practices and What’s New with In-App Purchases

对于一个标准的 IAP 流程,大致如下图所示

标准的IAP流程

翻译成中文就是

  1. 设置商品 ID(跟 ITC 上面的 ID 一致)
  2. 根据商品 ID 去苹果后台获取商品信息,这时会获得商品名字、价格等信息
  3. 把 IAP 的购买界面展示给用户,用户可以同意购买并点击购买按钮。
  4. 用户授权购买,客户端向服务器发送购买请求。
  5. 此时购买流程状态变更,客户端根据苹果规定的状态机流程处理回调
  6. 如果购买请求验证通过,客户端此时解锁内容或者提供金币。
  7. 至此,整个交易流程结束。

以上是苹果文档上的流程,应用到 App 的实际操作中,我们还应该包含以下流程

  1. 营销活动配置
  2. 试用功能控制
  3. 沙箱联调流程
  4. 安全性校验

本次 WWDC 议题很好地描述了上述的细节

App 定价策略

一般的 App 营销活动有如下的类型

App 营销活动

iOS11.2的时候,苹果增加了如下类和接口便于开发者实现上述业务流程(只针对自动续期订阅的场景)

@available(iOS 11.2, *)
open class SKProductDiscount : NSObject {

    
    @available(iOS 11.2, *)
    open var price: NSDecimalNumber { get }

    
    @available(iOS 11.2, *)
    open var priceLocale: Locale { get }

    
    @available(iOS 11.2, *)
    open var subscriptionPeriod: SKProductSubscriptionPeriod { get }

    
    @available(iOS 11.2, *)
    open var numberOfPeriods: Int { get }

    
    @available(iOS 11.2, *)
    open var paymentMode: SKProductDiscount.PaymentMode { get }
}

以上的 api 对应于App Store Connect后台中设定的推介促销价,可以设置为折扣价格或免费试用,根据适当的条件,App 可以为符合条件的用户显示促销价。接下来我们先了解一些基本概念,然后再看看如何通过以上的 api 来完成对应的业务流程。

创建推介促销价

App Store Connect中可以为 App 内购买项目的每个订阅设置和管理推介促销价,并可以针对每个地区设置一个当前价格和一个未来推介促销价。如果创建的新价格出现重叠日期,则最新一次操作将覆盖现有的推介促销价。操作流程如下

  1. 在首页上,点按“我的 App ”,然后选择与该 App 内购买项目相关联的 App。
  2. 在工具栏中,点按“功能”,然后在左列中点按“App 内购买项目”。
  3. 点按自动续期订阅,前往“订阅价格”部分,然后点按“添加”按钮(+)。
  4. 选择“设置推介促销价”。
    Set_an_introductory_price_for_an_auto-renewable_subscription_iOS-tvOS-macOS_Create_an_introductory_price_Step4
  5. 选择想要提供折扣价格的地区,然后再点按“下一步”。
    Set_an_introductory_price_for_an_auto-renewable_subscription_iOS-tvOS-macOS_Create_an_introductory_price_Step5
  6. 选择开始和结束日期。如果想让推介促销价无限期使用,可以选择“无结束日期”。然后点按“下一步”。
  7. 选择“随用随付”、“提前支付”或者“免费试用”,然后选择合适的时限、货币和价格。点按“下一步”。
    Set_an_introductory_price_for_an_auto-renewable_subscription_iOS-tvOS-macOS_Create_an_introductory_price_Step7
  8. 苹果会根据您选择的价格点自动为所有地区计算价格,但您可以为特定地区设定不同的价格。在此处选择地区价格,然后点按“完成”。
    Set_an_introductory_price_for_an_auto-renewable_subscription_iOS-tvOS-macOS_Create_an_introductory_price_Step8

推介促销价有三种类型,分别如下

  1. 随用随付
    用户将按选定时限的每个结算周期支付推介促销价(例如,订阅的标准价格为 9.99 美元,推介促销价为前 3 个月每月 1.99 美元)。
    结算周期可设定以下时限:
  • 1 周订阅,1 至 12 周
  • 1 个月订阅,1 至 12 个月
  • 2 个月订阅,2、4、6、8、10 和 12 个月
  • 3 个月订阅,3、6、9 和 12 个月
  • 6 个月订阅,6 和 12 个月
  • 1 年订阅,1 年
  1. 提前支付
    用户将一次性支付选定时限的推介促销价(例如,订阅的标准价格为 9.99 美元,推介促销价为前 2 个月 1.99 美元)。可设定以下时限:1 个月、2 个月、3 个月、6 个月或 1 年。

  2. 免费试用
    用户在选定的时限内免费访问订阅。时限可以是 3 天、1 周、2 周、1 个月、2 个月、3 个月、6 个月或 1 年。

符合促销定价的用户

促销价的展示依赖于用户的购买状态,对于未购买的用户,肯定符合促销价的条件。如果用户已经付费,通过用户本地的付费收据,可以判断用户是否符合促销定价的条件。如果可以的话,通过App的业务后台进行一次预判断会更好。如果用户已付费,具体流程如下

  1. 读取用户本地存储的付费票据信息
  2. 判断票据信息中Subscription Trial Period字段和Subscription Introductory Price Period字段
  3. 如果这两个字段中有一个为true,表示用户已经正在享受促销定价周期,由于每个订阅群组的新顾客和重新订阅的顾客只可享受一次折扣价或免费试用,因此这个时候用户并不符合促销定价的条件

总结一下,符合促销定价的用户,只有以下两种类型

  1. 新的购买者
  2. 续期订阅的购买者并且之前没有享受过促销定价

相关代码

if SKPaymentQueue.canMakePayments() {
	let request = SKProductsRequest(productIdentifiers:
    self.productIdentifiers as Set<NSObject>)
    request.delegate = self
    request.start()
}

public func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse)
{    
	let products = Set<SKProduct>(response.products)
    
    if (products.count != 0) {
        for var i = 0; i < products.count; i++
        {
            let product = products[i] as? SKProduct
            let introductoryPrice: SKProductDiscount = product.introductoryPrice //这里获取促销定价的对象
            //假设我们设定了一个付费周期为3个月,头两个月以促销价结算的商品,并且付费方式后台设定是随用随付,它的值展开如下所示
            /*
             * introductoryPrice.price = 1.99
             * introductoryPrice.priceLocale.localizedString = "1.99" //本地化货币价格
             * introductoryPrice.subscriptionPeriod.unit = .month //以月为周期
             * introductoryPrice.subscriptionPeriod.numberOfUnits = 3 //1个周期为3个月
             * introductoryPrice.numberOfPeriods = 2
             * introductoryPrice.paymentMode = .payAsYouGo  //随用随付
            */
        }
    } else {
        println("No products found")
    }
    
    let invalidproducts = response.invalidProductIdentifiers
    
    for product in invalidproducts
    {
        println("Product not found: \(product)")
    }
}

试用版

我们同样可以为普通 App 设置试用体验,等用户付费以后再解锁相关功能,这是iOS12发布以后,App Store Review Guidelines中新增的功能(6月4日更新的版本)

3.1.1 In-App Purchase: Non-subscription apps may offer a free time-based trial period before presenting a full unlock option by setting up a Non-Consumable IAP item at Price Tier 0 that follows the naming convention: “14-day Trial.” Prior to the start of the trial, your app must clearly identify its duration, the content or services that will no longer be accessible when the trial ends, and any downstream charges the user would need to pay for full functionality. Learn more about managing content access and the duration of the trial period using Receipts and Device Check.

意思是你可以对你的付费 App 设置一个14天的免费试用期(此时用户实际上已发起了付费信息,安装 App 后在 bundle 里面含有票据),当试用期满以后,内容或服务不再允许访问,用户需要付费才可以继续使用。

注意

如果使用了该特性的 App ,在提交AppStore审核的时候,必须注意以下三点避免被拒风险

  1. App 在醒目的位置声明了试用期的剩余时间
  2. 给用户展示解锁功能的费用
  3. 明确说明,当试用期结束时,有哪些产品特性或本地内容将会丢失

如何使用

此功能是App Store Connect新增的功能,只需要前端配合展示相关内容信息即可

应用内评价

应用内评价是iOS10.3新增的功能,该功能使用了私有方法来分析当前是否是向用户询问评分的好时机,所以苹果强烈建议开发者不要在响应用户行为时调用此方法。例如,如果你把请求评分放在按钮触摸的回调函数里,但此时 iOS 可能决定不显示评分,所以用户就会认为 App 的功能出现了问题。另一方面,也不要太早让用户评分,最好等 App 运行几次之后再询问评分。尽管我们并不了解苹果的算法,但我们知道此方法的行为模式,所以最好在确定用户处于合适的时间时再进行调用。

注意

在本地调试代码的时候,也就是说每次进行请求调用,评分对话框都会显示,但无法提交评分。在Testflight中,请求都不会被通过,所以如果Testflight测试时评分对话框没有正确显示,不要慌张。App 上架后,就会在合适的时间显示对话框了。本次会议,苹果的工程师还特别强调了两种限制场景

  1. 对于一个应用而言,该函数的调用次数在同一设备上,365天内是有限制的。根据苹果人机交互文档中的说明,The system automatically limits the display of the prompt to three occurrences per app within a 365-day period,也就是说一年之内最多只会出现三次
  2. 用户可以在设置中关闭应用内弹窗功能

相关代码

if(canShowReview()/*一般是App业务的判断,如是否满足活跃用户等条件*/){
	SKStoreReviewController.requestReview() /*这个调用将会异步出现对话框,所以不会阻塞当前流程*/
	recordShowTimes()/*应该有节操地调用评分弹窗的接口*/
	/*
	 * 为什么这里需要增加showtime这个维度呢?考虑一下这个场景
	 * 已经判断是活跃分子,调用苹果api,这个时候苹果的算法判断需要出现弹窗
	 * 用户点取消
	 * 第二次App启动后,用户又触发了弹窗的满足条件,这个时候依然满足苹果的算法
	 * 弹窗再次出现
	 * 此时已构成骚扰,用户给差评的概率大大上升,因此showtime的这个维度还是非常值得加入到产品逻辑中去的
	*/
}

func canShowReview() -> Bool 
{
	// Local business rules
}

One more thing

应用内评价弹窗确实有次数限制,但是苹果还是给开发者留了一手,现在只需要给AppStore商店链接加上?action=write-review即可,如https://itunes.apple.com/us/app/itunes-u/id490217893?action=write-review,这样用户就可以跳转页面并弹起评价弹框。所以对于上述的流程,我们可以再扩展一下

if(canShowReview()/*一般是App业务的判断,如是否满足活跃用户等条件*/){
	if(isLimited()/*应用内评价窗口不能弹起*/){
		//感谢苹果爸爸的接口
		UIApplication.shared.openURL(myAppStoreLink()+"?action=write-review")/*跳转并提示用户进行评分*/
	}else{
		SKStoreReviewController.requestReview() /*这个调用将会异步出现对话框,所以不会阻塞当前流程*/
		recordShowTimes()/*应该有节操地调用评分弹窗的接口*/
	}
}

沙盒测试

由于付费功能如此重要,一般我们在上线前都希望先在测试环境下进行调试,苹果对于 IAP 也相应地给开发者配套了沙盒环境。

沙盒环境
对于沙盒测试来说,后台必须指定的账号进行测试,同时前端也要将SKMutablePayment对象的simulatesAskToBuyInSandbox设置为YES才会连到苹果的沙箱环境。 测试自动更新类 IAP 时,有一个不同之处:该购买是有周期的。订阅会于5次更新后作废。看到这里你肯定会想:"等等,要是我设置了每月的订阅,要测试过期得等到5个月后?" 实际上,自动更新的 IAP 在沙箱环境下,周期是会加速的,更新是按分钟或小时计算。对应的时间表如下所示

时间表

付费过程的处理

关于付费票据的处理过程,去年的WWDC2017已经有详细讨论过了,推荐阅读这篇文章,这次会议讨论了一些代码上的使用细节

Transaction Observer

对于SKPaymentTransactionObserver的回调处理,应该注册在 App 启动的时候,这样我们能第一时间知道用户是否完成购买流程

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

这是因为用户的购买流程跟 App 生命周期不挂钩导致的,典型的有以下几个场景

  • 用户杀死了 App
  • 用户需要更新帐号中的付费信息(此时已跳出 App )
  • App 闪退
  • 用户进行了订阅续期
  • 用户进入了推介促销价的流程
  • 用户跳出 App 输入推广码

Transaction State

正确理解购买流程中的各状态代表的意义,是非常关键的

public enum SKPaymentTransactionState : Int {
    case purchasing // Transaction is being added to the server queue.

    case purchased // Transaction is in queue, user has been charged.  Client should complete the transaction.

    case failed // Transaction was cancelled or failed before being added to the server queue.

    case restored // Transaction was restored from user's purchase history.  Client should complete the transaction.

    @available(iOS 8.0, *)
    case deferred // The transaction is in the queue, but its final status is pending external action.
}

结合上述代码,我们看一下下面的这个对应表

Transaction State Action Needed
.purchasing 不需要做什么,继续等待SKPaymentTransactionState的状态流转
.purchased 用户已完成付费,处理付费后的流程并调用finishTransaction方法
.failed 用户付费失败,处理付费失败的流程并调用finishTransaction方法
.restored 用户已完成付费,处理付费后的流程并调用finishTransaction方法
.deferred 不需要做什么,继续等待SKPaymentTransactionState的状态流转

deferred这个状态比较特殊,一般是家长控制导致的,即该购买请求会发送到孩子的家长帐号上,如果家长同意付费,该付费流程才能完成。但是deferred不会永远是deferred,有一个对应的过期时间如下图所示

过期时间

Finishing Transactions

对于购买完成的这个流程我们需要小心处理,不然一不小心就成为一个 App 漏洞造成较大损失(对于游戏项目而言确实是很可怕的事情)

  • 确保付费内容都已从苹果后台下载,然后才调用finishTransaction方法,不然该方法会阻止所有的下载流程,并且无法重新下载
  • 一定要通过业务后台去校验票据,不要在前端直接校验

用户票据的处理

这已经是一个很老的话题了,这次会议重新再整理几个要点

  • 票据验证一定是通过Server-to-Server的方式去验证,前端环境是不可信任的
  • 票据的失效日期比对,不要通过用户的本地时间(可以人为变更),而应该根据票据中的购买时间(已固化无法变更)去比对
  • 本地票据由一个经过签名的 PKCS #7 容器组成,开发者可以实现自己独有的的收据验证代码
  • 通过SKReceiptRefreshRequest的接口,可以向苹果后台重新请求用户的付费票据,这个流程是异步的
  • 通过遍历票据内容,可以判断用户是否继续处于试用版的流程中,默认认为用户处于试用版
  • 如果在App Store Connect后台中更新了收费类型,理应对用户已付费过的功能进行保持而不是重新收费

总结

苹果这次会议中介绍的新功能,有不少是针对自动订阅付费场景,自动订阅是指用户可以购买指定时间期限内的更新和动态内容,除非用户取消选择,否则订阅(例如杂志订阅等)会自动续订。而对于中国区的开发者而言,更常见的场景是一次性付费,如王者荣耀中的充值功能,微信读书中的买书功能等。因此暂时而言,苹果的一些关于 IAP 的新功能,在中国开发者的眼里并不是太有用。对比一下国内的一些 IAP 管理后台,如腾讯的米大师等,在功能的便利性和普适性上还有很大的差距,希望苹果后面能对IAP的相关功能投入更大的精力去开发和升级,为开发者提供更好用的应用管理平台。


参考资料

苹果文档 In-App Purchase Best Practices

Offering Introductory Pricing in Your App

为自动续期订阅设定推介促销价

Validating Receipts With the App Store

App Store Guidelines最新版

基于Swift最流行的StoreKit封装库

腾讯米大师文档

查看更多 WWDC 18 相关文章请前往老司机x知识小集xSwiftGG WWDC 18 专题目录