西瓜视频的 IAP 支付实践与 StoreKit 新特性

2106次阅读  |  发布于3年以前

基础介绍

在正式开始前,先给不熟悉 IAP 支付的同学介绍一些基础知识,以及西瓜视频使用 IAP 的具体场景。

In-App Purchase (IAP)

IAP 是苹果官方的支付方式,对于绝大多数虚拟物品的购买,苹果会强制要求走 IAP 支付,不然可能会遭遇拒审的问题。 IAP 又分为四种订阅类型,我们比较常用的是消耗型商品跟自动续期订阅。

会员中心与长视频详情页的业务流程

从左到右分别是 进入会员中心调用 SDK(客户端)进行支付显示会员权益 三个场景的示例。 用户在进入会员中心页面后,用户选择需要购买的特定档位后,发起支付,拉起苹果的支付弹窗,完成支付后,刷新会员中心页并鉴权成功后,给用户展示会员权益。 从左到右分别是 进入详情页调用 SDK(客户端)进行支付播放完整影片 三个场景的示例图。 用户在进入详情页观看试看片段后,用户选择需要购买的特定档位后,发起支付,拉起苹果的支付弹窗,完成支付后,刷新播放器进行鉴权,成功后播放完整影片。

正文

今天我的分享分为三个部分:

进入我们的第一部分,业务跟中台各自应该承担哪些职责。 对于中台来说,需要对业务屏蔽 IAP 的实现细节这些实现细节也包括一些订单的异常状态处理,包括掉单、补单的操作,与苹果校验订单真实性等。总而言之就是将业务所不关心的细节全部屏蔽。

而对于业务来说,就需要尽可能吸引用户下单,提高用户的转化率,优化购买流程,或者是购买更有吸引力的版权内容。同时还可能会做一些促销活动,例如买一个月会员,赠送 3 天权益等。那赠送的三天权益,或者说实际的会员到期时间,那就是业务自行管理的。同时业务还需要建立用户直接相关的数据面板,进行转化的分析等。

那么刚才是站在业务跟中台的角度去论述的。现在我们换一个思路,按客户端,服务端的角度来看问题。 对于客户端来说,一个完整的流程是在 APP 启动的时候,就开始接受 SKPaymentQueue 的消息,检查是否有未完成的订单,如果有的话就进行处理。当然如果用户发起了支付,也会进行处理。接着会解析 Transaction 中的数据,找到合适的业务方进行处理,发货完成,最后结束这笔未完成的订单。

由于大型 APP 中,例如说西瓜视频,会有多个独立的业务方,例如长视频、直播等,对应独立的业务服务器来校验订单。但是苹果的 SKPaymentQueue 只有一个。如果各个业务方都进行监听,并各自处理逻辑,会导致很多问题。

  1. 每个业务方都需要了解 IAP 细节,IAP 的实现逻辑繁杂,容易对异常情况处理不佳,最终导致线上用户体验受损。
  2. 如果业务方 A 错误处理了业务方 B 的订单,可能会导致用户权益的错误发放或是掉单,以及其他难以排查的问题。

因此由一个地方进行管控,接收 SKPaymentQueue 的消息,并进行订单的转发,是一个更合适的选择。并且还可以留下完整的日志,方便排查这笔 Transaction 最后转发到了哪个业务方,进行了怎么样的处理。 那么如何确定一个商品是哪个业务方处理的呢?由于苹果的机制,在一个 APP 下,每个商品都拥有自己独立的 Product ID,是 String 类型,不可重复。

因此每个业务方可以使用不同的前缀对各自的商品进行区分,这样就可以完成区分。当业务真的很复杂的话,还可以使用正则进行匹配。需要保证每一个 Product Id 只有一个业务方能够响应。这样方便判断这个业务方的回调能否处理。注意:每个业务线应该只处理自己的商品 ID,操作非自己业务的商品 ID 会有很大隐患(主要在延迟发货的情况下,会变成掉单)。

对于客户端来说,中台 SDK 也需要尽可能抹去的 StoreKit 细节以及各种不同的商品类型。对于业务方来说,只需要知道预订单的单号,以及 ReceiptTransaction 等信息。同时业务方也不需要对这些信息进行解析或别的操作,仅需要将信息透传至对应的服务端即可。

刚才是只有客户端,现在我们再把服务端的部分加进来。 业务的客户端在最上,业务的服务端在最下。中间是中台的客户端,也就是俗称的支付 sdk,以及中台的服务端。

流程比较长,但如果我们扣掉中台的部分的话,发现对于业务来说,完全不用区分是哪种商品类型,也不用处理异常情况,甚至不需要关心这笔订单是不是在本设备上发起支付的。需要的仅是发起支付,接受支付结果。 由于订单状态复杂,且苹果的校验服务不稳定,因此可能经常发生延迟发货的情况。因此延迟发货的处理,就变得十分重要。 这里只简单列举一个 case:苹果的通知会发往同一个 Apple ID 的所有设备上,因此可能你在 A 设备上购买,第一次收到购买成功的通知却是在 B 设备上,因为 A 设备后续可能未曾启动 APP。面对这种情况,还需要查询 APP 内自有的同一账号的近期内的预订单记录,并查询到最接近的一笔,进行发货。 我们进入下一部分,建立数据看板。 这一块重点是在客户端的看板。中台那里会有 IAP 解析的监控,例如订单校验超时的次数,平均接口耗时等。基本都是服务端校验的事情了,与客户端没有太大关系,这里就不再展开。

那业务方是否还需要自己建立一些客户端上的监控呢?是需要的。众所周知,服务端的监控耗时,如 pct50,pct90 并不能完全表现为用户感知的刷新时间。同时掉单可能是中台服务验证超时,也有可能是业务服务端发货逻辑写的有问题。同时支付中台的问题也会反应到业务的埋点上,可以辅助共同排查问题。

另外一个原因是,我们无法从苹果的 API 返回值准确判断,这个用户是不是“掉单”了,因为苹果给我们的结果都是“用户取消了支付”。掉单的概念是,用户付了钱,但是却没有享受到权益。因此只能退一步,看别的有价值的大盘数据来侧面验证 IAP 服务的稳定性。

当我们站在用户的视角,他关心的就是点击按钮后并支付完成后,能不能看到视频 / 能不能享受到权益。如果不能看到,那就会比较窝火,进而造成客诉。因此业务关注的点,实际应该由用户操作的路径中进行提炼。 结合我们的业务场景,最终在创建订单、支付结果、支付后鉴权结果,三个节点建立进行监控。

同时指标需要多维观察,除了阈值报警外,波动报警(参考窗口)也可辅助排除误报。

在建立了完善的埋点之后,应对客诉事件时,进行补发也会更加方便,甚至可以根据埋点做自动化脚本辅助判断。

一般来说,用户投诉掉单时,会附上苹果发到邮箱的收据截图。在今年的 WWDC21 之前,我们是无法判断这收据的真伪的。当然预计在今年年底,我们就可以通过 Server API,通过传入 Order ID 去向苹果查询订单真伪了。

举个例子,我们可以判断用户是否在订单发生前一段时间内是是否发生过支付行为(创建过预订单),来辅助我们判断是否应该给用户补发。当然具体的风控策略会远比这个复杂,在这里就不展开了。

我们进入分享的第三部分,StoreKit 在 WWDC20/21 上都带来了一些非常实用的新特性,对 IAP 的开发体验带来了极大的提升。

我们来回顾一下之前沙盒环境测试是一个多么复杂的流程。 需要去苹果的后台创建新的 IAP 档位,且 product id 是一次性的,申请错了会很麻烦。然后等待。创建新的沙盒账号。 如果需要测试首购优惠,则需要创建很多沙盒账号,并且来回切换。 测试自动续期订阅时,由于过期与否是苹果控制。且 1 个月的单次过期是 5min,一年的单次过期是 30min。如果苹果判定自动续期成功 2 次,你就需要等待 1h。是否续期成功,我们都无法控制。 同时由于沙盒账号没有管理页面,我们也无法进行主动的取消,这部分的测试必须上线再进行。好在一般来说订阅了一次之后,下一次扣款在一个月以后,在这期间,服务器将逻辑处理好即可。 更不用说有时候沙盒环境会无法连接,或者无法登录沙盒账号 这种服务不稳定带来的问题了。体验是极其糟糕的。

而在 WWDC20 上,苹果为我们带来了右图所示的 StoreKit Configuration File(下简称 StoreKit File),上述的问题都能解决。 如图所示是 StoreKit 文件创建一个订阅组的示范,图中圈出的部分是订阅组的优先级,跟 AppStore Connect 上的设置是一样的,可以有 1-3 三种优先级。我们可以看到,功能的支持还是十分完善的。 测试商品时,选项也是十分齐全。图中 1 是包括首开优惠,2 是促销优惠,都是可以进行设置的。 左图是可以本地管理历史订单,可以在控制台上选择一笔订单进行退款。方便开发者对退款的场景进行测试。 在左图的面板里,还可以取消订阅的,尽可能模拟可能发生的用户行为。

右图可以设置 自动续期订阅的过期时间,曾经苹果的自动续期订阅会由后台来自动帮你续期,或者续期失败。而一年的订阅产品,一次等待需要半小时,十分浪费时间。有了 StoreKit File 之后,对于升降级的测试,也更方便,不用等待 30min 再进行,自由决定时间的流逝速度。

右图中还有控制家长监控购买(Ask to Buy)。

综上,我们就在 APP 内,进行完整的,带支付流程的单元测试,不用再等待沙盒的商品生效。

对 StoreKit File,做个总结,极大节约了开发自动续费时的开发以及测试成本。当然因为是测试的票据,所以需要服务端配合对检验订单的过程进行一些改造。

在 iOS14 的设备上,设置 - App Store - 翻到最下面登陆沙盒账号之后,单击沙盒账号,再点击 Manage 就可以进入管理页面。iOS14 之前,沙盒账号是没有这个管理页面的。在这个页面里可以做到:

  1. 切换购买项目,可以测试同一个 Group 内的升降级
  2. 重制享受过的首开优惠,可以同一个沙盒账号反复测试首开优惠,而不用新开账号
  3. 取消订阅

下面我们会介绍 StoreKit2 的新特性,这些特性全部只有 swift 的 API,且仅对 iOS15 生效。但其中也有一些新特性,我们可以轻松接入。

  1. Product type 表示商品类型,一一对应前置知识里的 IAP(In ‑ App Purchase) 的四种订阅类型。特意在上面介绍的时候直接使用了跟这里相同的名字。
  2. Subscription info,只有 .autoRenewable 类型的商品才会有这个属性,而这个属性里包含了一个最有用的属性,isEligibleForIntroOffer,我们一会会进行展开。其余还有一些常用的属性,如 promotionalOffers 等,我们就不再展开了。
  3. BackingValue,这是一个苹果预备的字段,方便以后在推出新的特性后,老版本的 iOS 系统也可以使用新的内购类型。暂时没有看到使用,期待后续的更新。

isEligibleForIntroOffer,这个属性的放出可以说是解决了判断能否享受首购优惠重大难题。并且这个属性的接入不需要迁移整体的支付流程至 Swift,只是接入获取 Product 的部分即可。 在有这个属性之前,我们如果要判断这个 Apple ID 是否能享受首购优惠 (IntroductoryOffer) 是非常复杂的。 iOS11 还必须要服务器帮助,iOS12 虽然可以本地判断了,但依旧麻烦。简单介绍下 iOS12 的流程。

Purchase 方法调用后就能发起购买,不得不说,新 API 这个只需要一句话就能发起调用还是很舒服的,对使用方屏蔽了 SKPayment 与 SKPaymentQueue 的调用。

let result = try await product.purchase()

Purchase 方法同时还提供了一些自定义的参数,可以在调用 purchase 方法的时候传入一些信息,其中 appAccountToken 是最有用的,能解决延迟发货或掉单时无法准确补发的问题。

之前我们往往会将需要保存的预订单信息放到 SKPayment.applicationUsername 中去,但是实际情况中我们发现,这个参数总是会有丢失的情况,且苹果已经明确标注这个字段不可信。因此在购买前,需要提前创建一个预订单,将设备账号等信息储存至服务端,在服务器层面做一次映射,以应对延迟发货的问题。

希望这个参数足够稳定,不会在实际使用中丢失,建议在适配后进行观察。

对于 custom 参数,疑似是苹果为后续升级也能兼顾到低版本系统准备的。在 Beta 版本中尝试往其中注入自定义参数,这些自定义参数在 Transaction.payloadData 属性中也没有返回。或许是只有苹果限定的值才会生效。这也对上了上一章中提到的为后续低版本兼容准备的 BackingValue。

如果可以由我们自行注入参数那就是最好的了,但这个得等正式版放出后再进行验证。苹果官方在本 session 中并未展开。 对这部分新特性做个总结,如果你的 APP 对自动续费需求强烈,且产品形态经常使用 首购优惠 (Introductory prices),那么接入 StoreKit 2 绝对不容错过,实用性很高。appAccountToken 也有望解决困扰已久的掉单补发难题,开发者也能在应对恶意退款时做出对应的处理。

作为代价,因为这些新 API 仅对 iOS15 生效,对于需要支持老 iOS 版本的,原本的代码都无法舍弃,不仅是客户端,业务的服务器也将面临维护两套支付代码的窘境。是否值得接入,就由各位自行拿捏吧。 此前想要在 iOS 平台上退款还需要面临一个问题,就是退款申请页面的入口实在很隐蔽。当消费者出于某种原因想要退款就需要到苹果官网进行操作,但在其页面上并没有关于这一点的一级或二级入口,而直接搜索“退款”,则只会被引导到 Apple Store 的退款,也就是关于苹果硬件设备的退款页面上,但真正的 App Store 退款地址却是 reportaproblem.apple.com。

在未来的 iOS 15 中,用户能够直接在 APP 中完成退款操作的体验,无疑会极大的方便消费者。即便你一时冲动进行了不理智的消费,最起码可以现场就在 APP 中直接申请操作,而不用搜索“ iOS 退款”而被引入黑产团队的陷阱中。可能以后,能在 APP 内退款,也会成为一条审核标准。

结语

关于 IAP 这一块,入门成本一直很高。部分原因是苹果之前的 API 设计不友好,且服务本身不如支付宝或微信一样稳定,在这两年的 WWDC 中我们可以看到苹果在这方面的努力。

篇幅所限,想详细了解 StoreKit 的新特性的话,可以查看这几篇文章。 【 WWDC21 10114 】 初见 StoreKit 2 【 WWDC21 10175 】 IAP 用户退款与客诉处理优化 【 WWDC21 10174 】 IAP 后台通信优化与实践 【 WWDC20 10659 】 介绍 Xcode 中的 StoreKit 测试

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8