注意:文章中讨论的 IAP 是指使用苹果内购购买消耗性的项目。github
此次为你们带来我司 IAP 的实现过程详解,鉴于支付功能的重要性以及复杂性,文章会很长,并且支付验证的细节也关系重大,因此这个主题会包含三篇。编程
第一篇:[iOS]贝聊 IAP 实战之满地是坑,这一篇是支付基础知识的讲解,主要会详细介绍 IAP,同时也会对比支付宝和微信支付,从而引出 IAP 的坑和注意点。后端
第二篇:[iOS]贝聊 IAP 实战之见坑填坑,这一篇是高潮性的一篇,主要针对第一篇文章中分析出的 IAP 的问题进行具体解决。数组
第三篇:[iOS]贝聊 IAP 实战之订单绑定,这一篇是关键性的一篇,主要讲述做者探索将本身服务器生成的订单号绑定到 IAP 上的过程。服务器
不用担忧,我历来不会只讲原理不留源码,我已经将我司的源码整理出来,你使用时只须要拽到工程中就能够了,下面开始咱们的内容 。微信
源码在这里。app
上两篇文章已经针对 IAP 的九个大的问题中的八个问题进行了详细的讲解,若是你没有看上一篇文章,建议你先去看一下再回来,由于这三篇文章是按部就班的。上一篇文章解决了第一篇文章提出的九个问题中的八个,还剩下一个,这一个问题至关关键,因此单独用一篇文章来说解。ide
到如今为止,是否是感受全部的问题都指挥若定,内心有数了?post
那只是假象,show me the code
,编程不是纸上谈兵,而是须要亲自动手实践,细节是魔鬼。有位前辈说:“一样是一个 for 循环,你写在这里只值 5 毛钱,可是我写在那里就值 5 万块”。固然这不是炫耀,而是想夸张的表达编程中细节的重要性。
前两篇讲的内容已经能够串起来一个相对严谨的支付流程了。可是要把整个流程串起来,还差了关键的一步,而这一步并不是易事,至少做者走这一步就很是不容易。
这一步是什么呢?就是要将公司服务器生成的订单号 orderNo
绑定到苹果的交易 paymentTransaction
上。第一篇文章中说了,苹果的规范是用一个 product
生成一个 payment
,而后将这个 payment
推入到 paymentQueue
之中,最后咱们成为交易事务的监听者,在监听方法里拿到交易的 paymentTransaction
,咱们放进去一个苹果的 payment
实例,最后获得的是一个 paymentTransaction
。
问题来了,咱们最后拿到的是一个 paymentTransaction
,苹果只告诉咱们 哪个 paymentTransaction
成功了,而咱们根本就无法将咱们本身的订单号绑定到这个成功的 paymentTransaction
上,从而创建映射,正确的去后台验证这个订单。
而将咱们本身的订单映射到 paymentTransaction
又是必须的,下面就一块儿来看看这揪心的最后一步是怎么走的。
我不相信苹果会连这个问题都没想到,因而就去找文档, paymentTransaction
里有一个 payment
,这个 payment
就是咱们本身用 product
建立的,可是 payment
的全部属性都是 readonly
的,无法更改。好在有一个 SKMutablePayment
,这个家伙的有些属性是 readwrite
的,其中有一个属性叫作 applicationUsername
。
var applicationUsername: String
An opaque identifier for the user’s account on your system.
复制代码
这是一个 iOS 7 之后才有的属性,能够容许咱们本身往 payment
里保存一个字符串类型的数据。
这不就恰好嘛,我就说苹果不可能连这么简单的需求都想不到。好,就用这个属性就 OK 了。当用户点击购买的时候,首先去后台生成一笔交易,而后拿到交易订单号 orderNo
,而后将这个订单号保存到 payment
上面,而后在苹果支付成功的回调中获取到 paymentTransacion
,而后从这个 paymentTransacion
的 payment
中将保存的订单号取出来,那么就能实现咱们本身的订单号和苹果的订单一一映射,perfect!
做者刚开始就是按照这个原理去实现的,直到功亏一篑。
事情是这样的,做者公司的测试发现一旦某个订单未推入 keychain
中持久化,而是等重启的时候再去检查未持久化的交易而后将其推入持久化队列的时候,就会产生崩溃,从 bugly 后台看到的数据显示,是由于取 applicationUsername
的时候取不到。而后我就连上电脑测试,发现只要将 APP kill 掉,再次去取以前保存的 applicationUsername
的时候就是 nil
。说到底就是苹果根本就没有给咱们存进去的信息作持久化,苹果本身的属性都有持久化,惟独 applicationUsername
没有。
“鸡肋鸡肋,食之无肉,弃之有味”,形象的表达了 applicationUsername
这个属性的尴尬。show must go on,仍是得继续寻找这关键一环的解决方案。
接下来我就尝试,既然苹果不给咱们的 applicationUsername
属性作持久化,那能不能咱们本身来作呢?
全部的交易都是有惟一的交易标识的,咱们若是能将全部的交易在 purchasing
状态就存起来,那么当某笔交易是 purchased
的时候,咱们就能以交易标识为引子去一堆以前保存的 purchasing
状态的 paymentTransaction
中找到对应的交易,而后取到咱们以前持久化的 applicationUsername
。若是这样能行得通,那咱们就又能把整个过程串起来了。
“理想很丰满,现实很骨感”。某笔交易状态仍是 purchasing
时,支付系统尚未为这笔交易分配交易标识,因此就算是存了,也没有办法在那笔交易的状态变为 purchased
时从以前持久化的数据中找到存的数据。
这个方案也只能做罢。
从以上两个尝试再结合苹果后台不对帐的风格,咱们大体能体会到,IAP 的设计思想就是不想让咱们可以将本身的订单关联到 IAP 的订单,这也符合苹果一向想控制一切的做风。
在真正的解决方案浮出水面以前,做者规划了一种**“粗放式的验证”**来应对这种窘况,下面咱们来说一下什么叫作“粗放式验证”。
咱们将进入 purchasing
的全部订单都持久化起来,而后此时虽然没有分配交易标识,可是产品标识仍是有的。等某笔交易到了 purchased
的时候,咱们用这个 purchased
的交易的产品标识去持久化的交易中将全部是这个产品标识的交易都取出来组成一个数组,而后任一取一笔进行验证,只要验证成功了,就算交易成功。
若是难以理解,那咱们就对着上面这个图来看看。咱们将本身的订单号存到交易里,而后将交易存起来,那么本身的订单号也获得了持久化。之后在 purchased
的时候去取任意一笔交易的时候(指定产品标识的),其实取的是咱们后台生成的任意一个交易订单号(指定产品标识的),而后将已经完成的 IAP 交易和咱们的订单号拼接组合起来进行验证。
这种方案确实是能达到咱们验证的目的。可是对于有洁癖的同窗来讲,这个方案只能算是过渡方案,称不上完美,更谈不上优雅,因此只能叫作“粗放式的”。并且有一个无法避免的问题是,咱们存的那么多 purchasing
状态的交易,只有少数能在使用之后删除,大多数都是无效的。可是咱们又没有一个契机能去清理这个持久化数据,由于咱们根本无从知道那个交易是有用的,哪一个是无用的。因此咱们只能所有保存,不敢清理,这样致使这个持久化数据愈来愈多,却没有清理的可能。
如今想明白了就会知道,以上的尝试迂迂回回,都是掉进了思惟惯性里了。咱们严苛遵循了古老的传统:先去本身服务器建立订单,再使用 IAP 交易。其实突破点就在这里,咱们后端的一个同事提出,先去苹果那里交易,交易完成之后再去咱们本身的服务器建立订单是否可行?
还记得第一篇文章中的这张图吗?
咱们调转支付流程之后,应该变成下面这样。
我不作解释了,聪明的你必定知道这个微妙的区别带来的极大的便利。至此,订单绑定获得了优雅的解决。
若是是按照这个逻辑来走的话,有一个很显而易见的逻辑缺陷,从 IAP 支付到咱们去后台建立订单这个过程有苹果支付的和咱们建立订单的延时。如今情景是用户 A 发起了支付,而后还未购买就退出了登陆,而后用 B 帐号登陆了,而后 IAP 支付成功,咱们将支付信息存进了以 B 的 userid
为 key 的帐户中,这样就会致使咱们去后台验证的时候会把钱充到 B 帐户中,以下图所示。
因此咱们在用户退出登陆的时候须要去检查他是否有未完成交易,若是有就要给个警告。可是仍是没办法完全解决掉这个问题,可是考虑到这个结果是用户的行为致使的,并且出现这个问题的概率不大,暂时就这样处理。
若是你确实有这方面的担忧,那就应该采用上面说的粗放式的验证,粗放式的验证是不存在这个问题的。