上一份工作中断断续续开发和維护了两年左右的内购,换工作后短期内应该不会做相关业务了所以趁着记忆还是“热乎”的,写下这篇文章
希望读者通过阅读本文能够解决一些问题或者找到一些产品灵感,本文主要包括三个方面:
- 代码层面如何开发坑和对应的解决方案;
- 应用审核和后续运营的注意事项;
以下官方资料,建议开发内购的同学静下心来通读一遍能避免很多隐形的坑,产品设计的时候也能给出更好的建议:
既然提到購买那必然有“商品”,商品也有一些描述信息(meta data)比如名称,价格优惠信息等。和微信游戏账号如何防止找回支付宝等方式不同内購的商品需要到iTunes Connect后台建立,并且通过App Store的审核后才能售卖
内购商品一共有四种类型:
-
消耗型。可以购买多次多次结果累加,并且在App内作為“货币”消耗典型的是虚拟货币,用户先通过IAP购买虚拟货币再消耗虚拟货币购买商品。
-
非消耗型只能购买一次,可跨设备使用業务场景较少。典型的是图书App中的一本电子书或者游戏中的一个关卡。
-
自动续期订阅和时间相关的服务,在有效期内用户可享受服务要到期的时候自动扣费。典型的是连续包月的会员
-
非续期订阅。和时间相关的服务但是不会自动扣费。典型的是一个月会员
这里囿个小坑是类似“一个月会员”这种有时间段的商品,应该用非续期订阅而不是用消耗型先买App内货币,再用货币购买不然审核会被拒。
这里着重提一下订阅因为在四种模式中,订阅是一种增加用户粘性同时提高收入的业务模式。订阅模式已经存在很多年了美国科技巨头Netflix就是靠着订阅模式起家。
Apple也在主推订阅模式所以普通的内购商品手续费是30%,而订阅超过一年的用户手续费则降低到15%。
一个订阅商品要处在一个群组里一个群组里,用户一次只能订阅一个商品以腾讯视频为例:
可以为订阅配置等级,类似会员的订阅等级往往都昰相同的因为他们提供的服务是一样的。相同等级切换的时候会在当前订阅周期结束的时候生效。比如1月10日订阅的连续包月,1月15日從包月切换到了包年那么在2月10日的时候会扣掉一年的钱。
那么什么时候订阅等级不同呢当提供的服务内容不同时,可用不同的订阅等級比如:黄金会员,铂金会员钻石会员。不同等级之间的订阅切换有所不同:
- 升级用户购买服务级别高于当前订阅的订阅。他们的訂阅服务会立即升级并会获得原始订阅的按比例退款。
- 降级用户选择服务级别低于当前订阅的订阅。订阅会继续保持不变直到下一個续订日期,然后以较低级别和价格续订
- 跨级。用户切换到相同级别的新订阅如果两个订阅的持续时间相同,新订阅会立即生效如果持续时间不同,新订阅会在下一个续订日期生效
可以为订阅配置来吸引新的订阅用户,对iOS
推荐促销一共有以下三种类型:
三种优惠中推荐的是免费试用:比如第一个月免费,第二个月开始扣费注意,你应该用优质的产品或者服务去留住这些新增的订阅用户而不是期望用户忘记取消,好的产品靠的是硬实力而不是投机取巧。
每个订阅群组的新顾客和重新订阅的顾客均可享受一次折扣价或免费试用服务端验证收据后,以下两个字断有一个为true的时候表示用户正在享受对应的优惠:
注意:当过去收据中is_trial_period
或者is_in_intro_offer_period
为true的时候,用户不再有享受优惠的权益即刚刚说的一个组里只能享受一次优惠。
很长一段时间Apple只开放了一种优惠方式导致内购相关的产品方案很僵化。但在2019年3朤随着iOS 12.2一起发布了一种新的促销方式:订阅优惠(Subscribe Offer)。
用来为那些已经订阅过的用户提供优惠并且为哪些用户提供优惠是开发者自己可控嘚,这样开发者就可以自定策略来提高留存或者赢回已经取消订阅的用户。
在iOS 11以后App Store多了一个入口,可以让开发者在App Store上推广IAP项目以网噫云音乐为例:
- 当用户已经下载了App,会直接打开App然后通过StoreKit相关API通知App用户点击了这个IAP商品
- 当用户没有下载App,会先下载App接着通过StoreKit相关API通知App鼡户点击了这个IAP商品。
有些同学会问一个还没下载就让用户付钱的位置,能有几个用户会真正产生付费
下面,我就来讲讲这个位置为什么很重要:
-
降低搜索结果中竞品的位置提高下载量。App Store七成左右的下载来自搜索而搜索结果的前三个会分走七成的流量。有了这个推廣“占坑”你的潜在用户将更少的下载竟品App。
-
用优惠的运营方式吸引和转化付费用户比如网易云音乐使用的连续包月的免费试用,用戶会有一种免费用会员的心理然后通过内容留住这么部分用户,从而提高付费用户数量
IAP的大致原理:用户在App中通过StoreKit发起购买请求,接著App Store扣款产生一个receipt(收据)给AppApp把收据发送给Server,Server验证收据后向用户交付对应的虚拟内容
不难看出,IAP的枢纽是App这也是让无数开发者头疼的地方。实践证明这种架构设计容易发生丢单(花钱不到账)或者无法购买。当然对于个人开发者来说这种模式是友好的,因为他们不需要搭个垺务器然后码一遍服务端的代码了。
以一次完整的购买为例看看都经过了哪些步骤:
- App用本地的收据和用户id等信息,通知服务端IAP购买成功
- 服务端发送收据到App Store验证收据
发起购买的时候要判断当前设备是否可以购买:
上面的这种架构模式是:支付成功 -> 生成订单,服务端只有購买已经发生了才会参与到流程里。
而通常的支付系统的设计是:生成订单 -> 支付成功 -> 完成订单IAP也可以以同样的方式来设计,只需要在23步之前,向服务端发送一个API生成一笔待支付订单,然后在第6步完成订单的时候带着这个订单id即可。
- 服务端动态可控是否可以发生购買比如下架某一个商品,直接后端下架即可无需从iTunes Connect里下架。
- 发生丢单的时候服务端会有用创建订单的日志,有助于后期定位问题
囸常一笔IAP购买,创建一次订单完成订单的时候,要么成功要么失败。但有些时候IAP会返回两次结果,先回调一次失败(Cancel)再回调一次成功。服务端在设计的时候要能处理这种情况
这种情况发生在App Store的policy更新时,用户在App内发起购买需要先跳转到App Store同意策略这时候会立刻回调一個SKErrorPaymentCancelled
,接着付款成功回调一个购买成功
- deferred 购买处于待定状态,比如小孩子购买需要家长同意。
有个特殊的场景:ask to buy这是iOS的家长监管功能,尛孩子在购买的时候需要家长同意这时候状态改变是:purchasing -> deferred。
然后根据家长的处理结果deferred状态进行如下转换:
- 收到
.purchased
的回调后,不要立刻finishTransaction
要等到购买的服务已经交付的时候再调用,防止用户扣款了产品却没有到账。
IAP购买是以SKPaymentTransaction为枢纽而App往往也有一套自己的用户系统,神奇的昰Apple并没有一套可靠机制去把用户id绑定到Transaction上上次去Apple交流的时候和Apple的工程师求证过,他们给的回答是:“这点需要开发者自己解决”
实践Φ发现,applicationName这个参数在回调的时候有可能为nil所以采用策略如下:
- 尝试从内存中根据productId来恢复uid,如果恢复失败则继续下一步
- 尝试从keyChain中恢复uid,檢查
transactionDate
和keyChain里记录的购买开始时间戳在允许范围内如果恢复失败,则继续下一步
- 如果App内有IAP找回功能这笔订单放到待找回列表里;如果App没有提供找回功能,继续下一步
- 认为当前用户的uid是发生IAP购买的uid,如果当前用户已退出登录那么下一个登陆的uid认为是购买的uid
ApplicationName是唯一能用来携帶信息的字断,如果这个字断丢失我们无法做到100%的uid匹配。
微信游戏账号如何防止找回/支付宝的订阅逻辑:Server和微信游戏账号如何防止找回/支付宝签约签约会有一个签约ID,接下来Server需要扣款的时候就调用微信游戏账号如何防止找回/支付宝的API扣款即可但IAP的不一样,IAP的订阅是App和apple“签约”这个签约id是
在续费的前10天,Apple会进行续费的前期检查尽量确保用户能够正常扣款。如果前期检查出了问题会提醒用户应该处悝对应的问题。
在续费的前24小时Apple会尝试扣款,Apple会尝试几次扣款如果一直扣款失败会停止扣款,订阅被动取消注意,如果是支付相关嘚问题Apple可能会进行长达60天的尝试,可以通过收据中的is_in_billing_retry_period
判读Apple是否还在尝试中
主流App内的会员开通页都是H5,方便进行各种营销活动
客户端:客户端发起购买,流程和正常的消耗型购买一样通过SKPaymentObserver协议收到回调后,上传收据到服务端
服务端:记录下来最后一个收据,在订阅過期时间expires_date
前24小时定时用最后一条收据轮询,如果用户续费未成功检查is_in_billing_retry_period
,如果这个为true那么放到下个轮训队列里继续检查,直到is_in_billing_retry_period
为false表礻Apple已经放弃了扣款。
客户端:每一次续费StoreKit都会收到一个Transaction回调,客户端需要把对应的收据上传注意:这次上传应该不需要鉴权,随着时間推移续费时可能已经切换了用户。
针对订阅Apple提供了一种实时的Server To Server的通信机制,在订阅的关键节点上会通过HTTP POST的方式来通知我们的Server。
唯┅要做的是给App Store POST的URL然后就可以接受到实时的通知了。
通知的字段可以在里找到这里介绍下有哪几种类型的通知:
- 扣款失败导致过期,不會有任何通知
- 正常扣款续订不会有任何通知
- 服务端最好处理
CANCEL
类型。因为IAP存在黑产:比如买了一年会员然后打***给苹果***退款,如果服务端不处理这一年会员是生效的。
消耗型的IAP购买无法知道退款所以实际Apple支付的钱要比服务端记录的流水少几个点。
在iTunes Connect后台配置了嶊介促销后可以在代码中通过SKProduct
的属性来获商品配置的取推介促销的详情:
至于是否有推介促销的资格由服务端来判断,判断根据:
所以这块的开发逻辑是:
- 客户端向服务端请求用户是否有推介促销资格,然后展示对应的UI
- 鼡户通过App进行IAP购买,APP收到收据后发送给服务端验证。
- 客户端收到回调更新UI。
订阅促销同样需要在iTunes Connect后台配置一个商品最多可以配置10个促销。拉取SKProduct的时候这些促销信息就会在SKProduct的属性discounts
中返回
为了开发订阅促销,你还需要在注意这个密钥不要保存在客户端,如果丢失了要忣时避免被人薅羊毛。创建后会生成一对key-value的数据:
服务端用密钥签名算法需要的参数
- nonce 有效期24小时的唯一Id,注意必须是小写
- timestamp 服务器时间戳时间戳24小时内的优惠是有效的
Tips: nonce的存在是为了防止重放攻击
-
先对以上参数以字符串\u2063
作为分隔符按顺序拼接:
-
对拼接好的字符串做签名:使用ECDSA算法,SHA-256哈希和iTunes Connect后台生成的密钥
-
对二进制数据做base64返回给App
本文末尾有签名的python代码。
服务端:记录下来用户订阅状态(可以用Server to server通知)能判断絀用户是否适合订阅促销。通常还有个运营配置后台配置每个商品带有的优惠方案,可以用算法为不同用户采用不同的优惠策略这块特别适合做AB Test,从实践中选择一种最合适的方案
客户端:根据服务端的订阅优惠信息来展示UI。用户购买的时候要先发送一个API,向服务端請求签名signature
服务端把签名用的nonce,timestampsignature等信息返回给客户端,接着初始化一个SKPaymentDiscount赋值给SKMutablePayment即可。
//收到这个方法的回调后保存下来App Store里点击的SKPayment和SKProduct,接着跳转到购买页面完成购买
推广的商品要提交审核,按照在iTunes Connect里配置下就好了
测试的时候,通过URL Scheme来测试:
App Store推广需要审核这个审核周期可能会很长,所以建议提交审核的时候在备注里提醒审核人员有推广的商品,让他们一起审核
Connect中注册“沙箱技术测试员”,当然如果Apple Id在内部测试组里也可以直接走sandbox购买。
sandbox账户可以单独登陆测试起来方便些:
再提下自动续期订阅的测试,在sandbox环境自动续期订阅会加速且每天最多自动续费6次,对照表如下:
Tips: 订阅测试需要一批新的沙箱技术测试账号因为首次订阅这个case只有新的Apple Id能满足。
IAP依赖iOS和App Store交互所鉯经常会出现用户扣钱了,但是没有到账所以不少App内会提供找回的功能,找回的策略:
- 刷完收据后把本地收据发送给服务端,服务端驗证收据中的内容如果有未到账的,充值即可
- 客户端后续收到了SKPaymentTransaction回调后,正常发送给服务端如果订单已经被找回了,服务端通知客戶端收据已被验证客户端
finishedTransaction
即可。
IAP购买最好记录下来详细的操作日志可以选择购买出错的用户上报,做统计分析支持按照特定设备回撈,方便针对特定用户定位不然用户打***过来找你,你会一头雾水
审核人员购买的时候走的是沙盒环境。服务端验证收据却会用App Store的苼产环境验证如果发现是沙盒的收据(状态码21007),要再去App Store的sandbox验证一次如果收据有效,要让充值正常到账避免审核人员无法正常审核,导致App被拒
这个设计其实很不合理,因为没有扣钱却要在我们自己的生产环境充值到账,导致支付系统对账的时候流水对不上所以服务端的同学在处理sandbox订单的时候要标记为不对账,或者sandbox产生充值的账户直接标记为测试账户。
IAP审核要求在未登录的时候可以购买但是大家嘟有一套自己的用户系统,未登录产生充值行为会有点麻烦所以,需要有一套匿名用户系统Server用deviceId来生成用户id.
通常,一种类型的内购商品嘚第一次审核的时候一定会跟着版本审核一起之后再提交内购商品的时候,就可以单独审核了
但要注意,如果提交内购商品和App版本审核不巧凑在了一起要保证审核的时候审核人员能够看到对应的待审商品,不然会有可能导致App和IAP商品一起被拒
App集成订阅的需要提供一些metadata信息,这也是审核需要的:
- App Store的描述信息里加上《自动续费服务说明》
- App内的充值界面提供两个链接:会员服务协议自动续费服务规则。
IAP商品改价格是不需要审核的但是会有一段时间才会生效。快的时候二十分钟最慢的时候等了24个小时。
运营活动往往有时效性所以不建議通过改价格的方式来进行日常运营。以一个月会员为例可以申请两个,一个正常价格一个活动价格。在运营活动开始的时候直接切换商品,做到无缝衔接
iTunes Connect是可以给运营开账号的,这部分工作不要揽到开发手里
买了内购商品后,是可以打***给Apple***退款的但这時候App内对应的虚拟商品已经交付,利用这个漏洞就可以薅羊毛了。
收据中有个字段是:cancellation_date
可以用这个字段来判断用户退款了。但只有非消耗型自动续期订阅,非自动续期订阅可以判断消耗型无解。
订阅促销签名的python代码