[iOS]贝聊 IAP 实战的订单绑定。[iOS]贝聊 IAP 实战的见坑填坑。

大家好,我是贝聊科技
iOS 工程师 @NewPan。

留意:文章被讨论的 IAP 是靠以苹果内购购买消耗性的品类。

大家好,我是贝聊科技
iOS 工程师 @NewPan。

留神:文章被讨论的 IAP 是乘用苹果内购购买消耗性的项目。

这次也大家带来自己司 IAP
的贯彻过程详解,鉴于支付功能的要紧和错综复杂,文章会格外丰富,而且出验证的底细呢涉重要性,所以这主题会蕴藏三篇。

这次也大家带来自己司 IAP
的贯彻过程详解,鉴于支付功能的重点以及错综复杂,文章会生丰富,而且付出证明的底细也关系重要性,所以这个主题会包含三首。

第一篇:[iOS]贝聊 IAP
实战的满地是坑,这无异首是付出基础知识的教授,主要会详细介绍
IAP,同时为会见对比支付宝和微信支付,从而引出 IAP 的坑和注意点。
第二篇:[iOS]贝聊 IAP
实战的见坑填坑,这无异篇是高潮性的同一篇,主要针对第一首文章被分析有之
IAP 的题目开展具体解决。
第三篇:[iOS]贝聊 IAP
实战的订单绑定,这无异首是重头戏的平等篇,主要描述作者探索以协调劳动器生成的订单号绑定到
IAP 上之进程。

第一篇:[iOS]贝聊 IAP
实战的满地是坑,这无异于篇是支付基础知识的授课,主要会详细介绍
IAP,同时也会比支付宝和微信支付,从而引出 IAP 的坑和注意点。
第二篇:[iOS]贝聊 IAP
实战的见坑填坑,这同首是高潮性的同等篇,主要对第一篇稿子被分析出底
IAP 的题材进行具体解决。
第三篇:[iOS]贝聊 IAP
实战的订单绑定,这同首是核心的相同首,主要描述作者探索用协调劳动器生成的订单号绑定到
IAP 上之进程。

并非顾虑,我尚未会只说原理不留源码,我曾用我司的源码整理出来,你用时不过需要甩到工程中不怕得了,下面开始我们的内容

不用操心,我莫会单纯谈原理不留源码,我早已以我司的源码整理出来,你以时仅需要甩到工程被不怕可以了,下面开始我们的内容

源码在这边。

源码在这边。

笔者写了一个受 iPhone X 去丢刘海的 APP,而且其他 iPhone 也可打,有趣味的言辞去 App Store 看看。点击前往。

及点儿首文章已针对 IAP
的九只特别的问题遭受之八个问题开展了详尽的教授,如果你没爱上一首稿子,建议您先去押一下再次回去,因为及时三篇稿子是渐进的。上同一首文章解决了第一篇稿子提出的九单问题遭受的八单,还余下一个,这一个题目相当关键,所以单独用同一篇稿子来上课。

落得同一篇之解析了 IAP
存在的问题,有九单点。如果您切莫亮是哪九独点,建议您先夺押一下上同首文章。现在咱们根据达同一首总结的问题一个一个来对号入座解决。

01.为什么这么重要?

交今天竣工,是勿是发有的题目都运筹帷幄,心里有数了?

这就是说不过是假象,show me the code,编程不是放空炮,而是欲亲自动手实践,细节是魔鬼。有个长辈说:“同样是一个
for 循环,你勾勒在此处仅仅值 5 毛钱,但是本人勾勒以那边就值 5
万片”。当然就不是投,而是想夸张之发表编程中细节的重大。

前面少首讲话的情节已得以拧起来一个对立谨慎的付出流程了。但是要拿全流程串起来,还不同了重大之均等步,而立即同步并非易事,至少作者走这同样步就是坏勿便于。

立刻等同步是什么为?就是要是拿店铺服务器生成的订单号 orderNo
绑定到苹果的交易 paymentTransaction
上。第一首文章被说了,苹果之科班是为此一个 product 生成一个
payment,然后用以此 payment 推入到 paymentQueue
之中,最后咱们成交易业务之监听者,在监听方法里以到交易的
paymentTransaction,我们放开进去一个苹果的 payment
实例,最后取得的是一个 paymentTransaction

题材来了,我们最后用到之是一个 paymentTransaction,苹果就报告我们
哪一个 paymentTransaction
成功了,而我们历来就没法用我们团结之订单号绑定到此成功之
paymentTransaction 上,从而确立映射,正确的去后台验证这个订单。

倘若以我们自己的订单映射到 paymentTransaction
又是须的,下面就是一同来瞧这揪心的尾声一步是怎动之。

作者写了一个叫 iPhone X 去丢刘海的 APP,而且其他 iPhone 也得玩,有趣味之言语去 App Store 看看。点击前往。

02. 堪当大任的 applicationUsername?

自我不相信苹果会并这题目且没悟出,于是就夺探寻文档, 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,然后由这
paymentTransacionpayment
中将保留的订单号取出来,那么就算能够兑现我们自己之订单号及苹果的订单一一映射,perfect!

笔者恰恰开头就是是本这个原理去贯彻之,直到功亏一篑。

业务是这般的,作者公司之测试发现如某个订单不推入 keychain
中持久化,而是等还开的时段又失去检查不持久化的市然后用那推入持久化队列的时节,就会见时有发生崩溃,从
bugly 后台看到底数码展示,是坐取 applicationUsername
的时光得到不交。然后自己虽连上电脑测试,发现只要将 APP kill
掉,再次去赢得前封存之 applicationUsername 的时候就是
nil。说到底就是是苹果向就是从未于我们怀着进去的信做持久化,苹果好之性都发出持久化,唯独
applicationUsername 没有。

“鸡肋鸡肋,食之任肉,弃的有料”,形象之达了 applicationUsername
这个特性的窘迫。show must go
on
,还是得累找这重要一围绕的化解方案。

01.越狱的题目

有关越狱导致的问题,总是充满了不明显,每个人都非同等,但是都是被了攻击造成的。所以,我们使用的章程大概粗暴,越狱用户一律不允许下
IAP
服务。这里我呢建议你这样做。我之源码中生一个器类用来检测用户是否越狱,类名是
BLJailbreakDetectTool,里面仅出一个艺术:

/**
 * 检查当前设备是否已经越狱。
 */
+ (BOOL)detectCurrentDeviceIsJailbroken;

只要你不思量使自己包的法子,也得以利用友盟统计里发出一个主意,如果您的类别对接了友盟统计,你
#import <UMMobClick/MobClick.h> ,里面来个类似方式:

/**
 * 判断设备是否越狱,依据是否存在apt和Cydia.app
 */
+ (BOOL)isJailbroken;

03.充分利用 purchasing?

连通下自己便尝试,既然苹果不被咱们的 applicationUsername
属性做持久化,那会无克我们和好来开也?

有着的贸易还是起唯一的交易标识的,我们要会拿所有的贸易以 purchasing
状态就怀起来,那么当某笔交易是 purchased
的下,我们尽管可知因市标识也引子去同堆积前封存的 purchasing 状态的
paymentTransaction 中找到相应的交易,然后抱到我们事先持久化的
applicationUsername。如果这样能够履行得通,那咱们就算以会管任何过程串起来了。

“理想很充实,现实挺骨感”。某笔交易状态还是 purchasing
时,支付体系还没有呢这笔交易分配交易标识,所以就算终于存了,也未尝法在那笔交易的状态变成
purchased 时于前面持久化的数额被找到存的数额。

这方案吧不得不作罢。

02.市订单的蕴藏

齐同首文章说交,苹果仅会于贸易成功以后通过
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
通知我们交易结果,而且一个 APP
生命周期只通一致糟糕,所以我们万万不能依赖苹果的这主意来驱动收据的查询。我们要开的是,首先使苹果通知我们交易成功,我们将用市数据好存起来。然后再说然后,这样一来我们虽可以解脱苹果通知交易结果一个生命周期只通一致破的噩梦。

那么这样乖巧的交易收据,我们在哪里吧?存数据库?存
UserDefault?用户同样推脱载 APP
就毛都没有了。这样的事物,只生一个地方存太相宜,那就是是
keychainkeychain 的风味就是率先安然无恙;第二,绑定 APP
ID,不会见丢,永远不会见丢弃,卸载 APP 以后重装,仍然能够从 keychain
里恢复之前的数目。

哼,我们今天开班计划我们的囤积工具。在初始前,我们如果用一个叔正在框架
UICKeyChainStore,因为
keychain 是 C
接口,很麻烦用,这个框架对那个举行了面向对象的包裹。我们本即因此框架进行打包。

#import <UICKeyChainStore/UICKeyChainStore.h>
#import "BLWalletCompat.h"

NS_ASSUME_NONNULL_BEGIN

@class BLPaymentTransactionModel;

@protocol BLWalletTransactionModelsSaveProtocol<NSObject>

@optional

/**
 * 存储交易模型.
 *
 * @param models 交易模型. @see `BLPaymentTransactionModel`
 * @param userid 用户 id.
 */
- (void)bl_savePaymentTransactionModels:(NSArray<BLPaymentTransactionModel *> *)models
                                forUser:(NSString *)userid;

/**
 * 删除指定 `transactionIdentifier` 的交易模型.
 *
 * @param transactionIdentifier 交易模型唯一标识.
 * @param userid                用户 id.
 *
 * @return 是否删除成功. 失败的原因可能是因为标识无效(已存储数据中没有指定的标识的数据).
 */
- (BOOL)bl_deletePaymentTransactionModelWithTransactionIdentifier:(NSString *)transactionIdentifier
                                                          forUser:(NSString *)userid;

/**
 * 删除所有的 `transactionIdentifier` 交易模型.
 *
 * @param userid 用户 id.
 */
- (void)bl_deleteAllPaymentTransactionModelsIfNeedForUser:(NSString *)userid;

/**
 * 获取所有交易模型, 并排序.
 *
 * @return models 交易模型. @see `BLPaymentTransactionModel`
 * @param userid  用户 id.
 */
- (NSArray<BLPaymentTransactionModel *> * _Nullable)bl_fetchAllPaymentTransactionModelsSortedArrayUsingComparator:(NSComparator NS_NOESCAPE _Nullable)cmptr
                                                                                                          forUser:(NSString *)userid
                                                                                                            error:(NSError * __nullable __autoreleasing * __nullable)error;

/**
 * 获取所有交易模型.
 *
 * @param userid 用户 id.
 *
 * @return models 交易模型. @see `BLPaymentTransactionModel`
 */
- (NSArray<BLPaymentTransactionModel *> * _Nullable)bl_fetchAllPaymentTransactionModelsForUser:(NSString *)userid
                                                                                         error:(NSError * __nullable __autoreleasing * __nullable)error;

/**
 * 改变某笔交易的验证次数.
 *
 * @param transactionIdentifier 交易模型唯一标识.
 * @param modelVerifyCount      交易验证次数.
 * @param userid                用户 id.
 */
- (void)bl_updatePaymentTransactionModelStateWithTransactionIdentifier:(NSString *)transactionIdentifier
                                                      modelVerifyCount:(NSUInteger)modelVerifyCount
                                                               forUser:(NSString *)userid;

/**
 * 存储某笔交易的订单号和订单价格以及 md5 值.
 *
 * @param transactionIdentifier 交易模型唯一标识.
 * @param orderNo               订单号.
 * @param priceTagString        订单价格.
 * @param md5                   交易收据是否有变动的标识.
 * @param userid                用户 id.
 */
- (void)bl_savePaymentTransactionModelWithTransactionIdentifier:(NSString *)transactionIdentifier
                                                        orderNo:(NSString *)orderNo
                                                 priceTagString:(NSString *)priceTagString
                                                            md5:(NSString *)md5
                                                        forUser:(NSString *)userid;

@end

/**
 * 存储结构为: dict - set - model.
 *
 * 第一层 data, 是字典的归档数据.
 * 第二层字典, 以 userid 为 key, set 的归档 data.
 * 第二层集合, 是所有 model 的归档数据.
 */
@interface BLWalletKeyChainStore : UICKeyChainStore<BLWalletTransactionModelsSaveProtocol>

+ (BLWalletKeyChainStore *)keyChainStoreWithService:(NSString *_Nullable)service;

@end

NS_ASSUME_NONNULL_END

俺们而封存之靶子是
BLPaymentTransactionModel,这个目标是一个型,头文件如下:

#import <Foundation/Foundation.h>
#import "BLWalletCompat.h"

NS_ASSUME_NONNULL_BEGIN

@interface BLPaymentTransactionModel : NSObject<NSCoding>

#pragma mark - Properties

/**
 * 事务 id.
 */
@property(nonatomic, copy, nonnull, readonly) NSString *transactionIdentifier;

/**
 * 交易时间(添加到交易队列时的时间).
 */
@property(nonatomic, strong, readonly) NSDate *transactionDate;

/**
 * 商品 id.
 */
@property(nonatomic, copy, readonly) NSString *productIdentifier;

/**
 * 后台配置的订单号.
 */
@property(nonatomic, copy, nullable) NSString *orderNo;

/**
 * 价格字符.
 */
@property(nonatomic, copy, nullable) NSString *priceTagString;

/**
 * 交易收据是否有变动的标识.
 */
@property(nonatomic, copy, nullable) NSString *md5;

/*
 * 任务被验证的次数.
 * 初始状态为 0,从未和后台验证过.
 * 当次数大于 1 时, 至少和后台验证过一次,并且未能验证当前交易的状态.
 */
@property(nonatomic, assign) NSUInteger modelVerifyCount;

#pragma mark - Method

/**
 * 初始化方法(没有收据的).
 *
 * @warning: 所有数据都必须有值, 否则会报错, 并返回 nil.
 *
 * @param productIdentifier       商品 id.
 * @param transactionIdentifier   事务 id.
 * @param transactionDate         交易时间(添加到交易队列时的时间).
 */
- (instancetype)initWithProductIdentifier:(NSString *)productIdentifier
                    transactionIdentifier:(NSString *)transactionIdentifier
                          transactionDate:(NSDate *)transactionDate;

@end

NS_ASSUME_NONNULL_END

纵使是片贸易的显要信息。我们当是目标实现归档和解档的法门后,就足以以这个目标归档成为平等段
data,也足以由同段落 data
中解档出这个目标。同时,我们需要实现这目标的 -isEqual:
方法,因为,因为我们当开展对象判等的上,要进行部分着重信息的比对,来确定两只市是否是一模一样笔交易。代码太多矣,我便不粘贴了,细节尚需而自己下载代码进去看。

今日回 keyChain 上来。每个 BLPaymentTransactionModel
对象归档成一个 NSData,多个 data
组成一个会合,再以是集归档,然后保留在一个以 userid 为 key
的字典中,然后再针对字典进行归档,然后又保存至 keyChain 中。

呼吁牢记是数额归档的层级,要不然,实现文件里看起有些傻。

04.粗放式验证?

由以上两单尝试再度结苹果后台不对准账的品格,我们约能体味至,IAP
的统筹思想便是勿思量被我们能够用好之订单关联到 IAP
的订单,这也合乎苹果稳定想操纵总体的作风。

当委的缓解方案浮出水面之前,作者规划了平等栽“粗放式的辨证”来应本着这种困境,下面我们来讲一下啊叫“粗放式验证”。

咱将登 purchasing
的备订单还持久化起来,然后此时虽说从未分配交易标识,但是活标识或有的。等某笔交易至了
purchased 的时,我们因而这 purchased
的市的出品标识去持久化的贸易中将有是这个活标识的市且收获下做一个反复组,然后凭一获得一画进行验证,只要说明成功了,就算交易成功。

设难以掌握,那咱们虽本着正值上面是图来瞧。我们用好的订单号存到市里,然后以交易存起来,那么友好的订单号啊博得了持久化。以后当
purchased
的下失去取自由一笔画交易的时候(指定产品标识的),其实取得的是咱后台生成的任意一个市订单号(指定产品标识的),然后用都到位的
IAP 交易及我们的订单号拼接成起来进行验证。

这种方案确实是会达成我们作证的目的。但是对有洁癖的同桌来说,这个方案不得不算是过渡方案,称未上完美,更称不齐优雅,所以只能叫做“粗放式的”。而且发生一个无奈避免的题目是,我们怀着的那么基本上
purchasing
状态的贸易,只有个别力所能及当行使后去,大多数且是不行的。但是咱还要不曾一个契机能去清理是持久化数据,因为我们从不许知道老交易是中的,哪个是无效的。所以我们不得不全部保留,不敢清理,这样造成这个持久化数据更是多,却没清理的也许。

03.证实队列

交今日为止我们得以对交易数据进行仓储了,也就是说,一旦 IAP
通知我们发出新的打响之市,我们立即把这笔交易有关的多少易成为一个市型,然后把这个模型归档存到
keyChain,这样我们尽管可知将说明数据的逻辑独立出来了,而无用依赖 IAP
的回调。

今我们初步考虑如何根据已经部分数据来达到流传我们自己之服务器,从而令我们的服务器向苹果服务器的查询,如下图所著。

我们可以计划一个阵,队列里来眼前待查询的交易 model,然后将 model
组装成一个 task,然后以这 task
中为我们的服务器发起呼吁,根据服务器返回结果还发起下一样破呼吁,就是上图的俾方式
5
,这样形成一个闭环,直到这个行列中持有的模子都给拍卖了了,那么队列就高居休眠状态。

比方首先不成让队列执行之发出四种植情况。

率先种是初始化的时刻,发现 keyChain
中尚闹无发出处理完需要验证的交易,那么这就算开从 keyChain
动态筛有多少初始化队列,初始化完以后,就好开于服务器发起验证请求了,也就是俾方式
1
。至于何以就是动态筛,因为这里的职责产生优先级,我们当会见再说。

仲栽使任务履行之艺术是,当前行处于休眠状态,没有任务要履行,此时用户发起购买,就会直接以手上贸易放到任务队列中,开始通往服务器发起验证请求,也就是俾方式
2

其三种植是用户从无网络及闹网络的时刻,会去对 keyChain
做同赖检查,如果生没有产生处理了的贸易,一样会朝服务器发起呼吁,也就是俾方式
3

季栽是用户从后台进入前台的时光,会错过对 keyChain
做同样差检查,如果发无发生处理完毕的交易,一样会往服务器发起呼吁,也即是教方式
4

发了方四种植档次的触及验证的逻辑下,我们尽管能够太酷程度确保拥有的市且见面往服务器发起验证请求,而且是永不停息的拓,直到有的贸易还认证了才见面停。

才说自 keyChain
中获得多少来一个动态筛的操作,这是啊意思呢?首先,我们为服务器发起的辨证,不必然成功,如果失败了,我们即将被此市型打上一个标记,下次证明的上,应该事先验证那些无给起及号的贸易型。如果未从标记,可能会见出现一直于证明和一个市型,阻塞了外贸易型的求证。

// 动态规划当前应该验证哪一笔订单.
- (NSArray<BLPaymentTransactionModel *> *)dynamicPlanNeedVerifyModelsWithAllModels:(NSArray<BLPaymentTransactionModel *> *) allTransationModels {
    // 防止出现: 第一个失败的订单一直在验证, 排队的订单得不到验证.
    NSMutableArray<BLPaymentTransactionModel *> *transactionModelsNeverVerify = [NSMutableArray array];
    NSMutableArray<BLPaymentTransactionModel *> *transactionModelsRetry = [NSMutableArray array];
    for (BLPaymentTransactionModel *model in allTransationModels) {
        if (model.modelVerifyCount == 0) {
            [transactionModelsNeverVerify addObject:model];
        }
        else {
            [transactionModelsRetry addObject:model];
        }
    }

    // 从未验证过的订单, 优先验证.
    if (transactionModelsNeverVerify.count) {
        return transactionModelsNeverVerify.copy;
    }

    // 验证次数少的排前面.
    [transactionModelsRetry sortUsingComparator:^NSComparisonResult(BLPaymentTransactionModel * obj1, BLPaymentTransactionModel * obj2) {

        return obj1.modelVerifyCount < obj2.modelVerifyCount;

    }];

    return transactionModelsRetry.copy;
}

05.打破思维惯性

而今想明白了就算见面掌握,以上的品迂迂回回,都是少进了思想惯性里了。我们严格遵循了古的传统:先去协调服务器创建订单,再使
IAP
交易。其实突破点就于这里,我们后端的一个同事提出,先去苹果那里交易,交易完成后又失我们友好之服务器创建订单是否可行?

尚记第一篇稿子被的立刻张图为?

咱调转支付流程后,应该成为下面这样。

自己弗开解释了,聪明之而早晚懂这个神秘的区分带来的翻天覆地的惠及。至此,订单绑定得到了优雅的缓解。

04.杀入新市

上面说明队列里我还有压入情景没有解释,压入观有三种状态。

先是种植是出现意外,就是初始化的时,如果出现用户刚好交易完毕,但是 IAP
没有通知我们交易就的场面,那么此时再也错过 IAP
的市队列里检查一遍,如果产生没出叫持久化到 keyChain 的,就一直杀入
keyChain 中展开持久化,一旦进入 keyChain
中,那么这笔交易就会为正确处理,这种气象以测试环境下经常出现。

亚种是常规贸易,IAP 通知交易成功,此时用市数据压入 keyChain 中。

其三栽及率先栽类似,用户从后台进入前台的早晚,也会见去检查一全副沙盒中起无出无出持久化的贸易,一旦产生,就把这些交易压入
keyChain 中。

点三个压入情景,能顶可怜程度达到保险我们的持久化数据可知同用户真正的贸易并,从而防止苹果出现交易得逞也尚未通我们要造成的
bug。

06.方案缺陷分析

如果是仍这逻辑来运动来说,有一个颇明白的逻辑缺陷,从 IAP
支付及我们去后台创建订单是进程发生苹果支付的以及咱们创建订单的延时。现在情景是用户
A 发起了开发,然后还非购买就离了登录,然后据此 B 账号登录了,然后 IAP
支付成功,我们拿开信息存进了盖 B 的 userid 为 key
的账户被,这样就算会招我们失去后台验证的早晚会拿钱假冒到 B
账户被,如下图所展示。

用我们在用户退登录的时段需要去检查他是不是出不就市,如果起且叫个警示。但是还是无办法彻底解决掉这题目,但是考虑到这结果是用户的行事致使的,而且出现这个题材的几乎率不充分,暂时虽如此处理。

假如您真的发这上面的担心,那即便当以地方说之粗放式的辨证,粗放式的求证是未有是问题之。

05.种结构总结

至如今得了,我们的组织已有了约了,现在咱们来总一下咱现在之品类组织。

BLPaymentManager 是交易管理者,负责和 IAP
通讯,包括商品查询及购买功能,也是贸易状态的监听者,对接沙盒中收据数据的获得与换代,是咱任何支付的入口。它是一个单例,我们的证实队列是挂于它们身上的。每当发生新的贸易上的时光(不管是啊状况进来的),它还见面拿这笔交易丢给
BLPaymentVerifyManager,让 BLPaymentVerifyManager
负责夺验证这笔交易是否行得通。最后,BLPaymentVerifyManager 也会和
BLPaymentManager 通讯,告诉 BLPaymentManager 某笔交易的状态,让
BLPaymentManager 处理掉指定的交易。

BLPaymentVerifyManager
是证明交易队列管理者,它里面生一个消证实的市 task
队列,它负责管理这些队列的状态,并且让这些职责的尽,保证每笔交易认证的先后循序。它的中发生一个
keyChain,它的阵中的任务还是自 keyChain
中初始化过来的。同时她也管理方keyChain 中之数,对keyChain
进行增删改查等操作,维护keyChain 的状态。同时为和 BLPaymentManager
通讯,更新交易的状态(finish 某笔交易)。

keyChain
不用说了,负责交易数额的持久化,提供增删改查等接口给其的官员使用。

BLPaymentVerifyTask 负责和服务器通讯,并且用报道结果回调出来为
BLPaymentVerifyManager,驱动下一个说明操作。

我的稿子集合

脚是链接是自身具备文章的一个成团目录。这些文章是涉及实现之,每篇文章中都有
Github
地址,Github
上还来源码。

本人之章集合索引

06.收条不联合处理

发生同行报告说,IAPbug,这个 bug
就是显著通知交易就成功了,但是去沙盒中赢得收据时,发现收据为空,这个问题吧是使切切实实应对之。

兹开了以下的处理,每次和后台通讯的结果归为三类,第一好像,收据有效,验证通过;第二看似,收据无效,验证失败;第三像样,发生误,需要再验证。每个
task 回来还是仅发生或是及时三种植情形的平种,然后 task
的回调会为班管理者,队列管理者会把回调传出来被市管理者,此时交易管理者在脚的代办方中创新最新的收据,并把新收据还传被班管理者,队列管理者下次发起呼吁虽是使新型的收据进行认证操作。

@protocol BLPaymentVerifyTaskDelegate<NSObject>

@required

/**
 * 验证收到结果通知, 验证收据有效.
 */
- (void)paymentVerifyTaskDidReceiveResponseReceiptValid:(BLPaymentVerifyTask *)task;

/**
 * 验证收到结果通知, 验证收据无效.
 */
- (void)paymentVerifyTaskDidReceiveResponseReceiptInvalid:(BLPaymentVerifyTask *)task;

/**
 * 验证请求出现错误, 需要重新请求.
 */
- (void)paymentVerifyTaskUploadCertificateRequestFailed:(BLPaymentVerifyTask *)task;

@end
您还足以关心自身自己维护的简书专题 iOS开发心得。这个专题的稿子都是真实的干货。如果你来问题,除了当篇章最后留言,还得当微博 @盼盼_HKbuy达到为本人留言,以及走访我的 Github。

07.注意点

  • 于 iOS 7
    开始,苹果之收据不是每笔交易一个收条,而是用装有的交易收据组成一个集结在沙盒中,然后我们在沙盒中取得到之收据是时下所有收据的集聚,而且我们也未了解当前收据里都生哪些订单,我们的后台也非懂得,只有
    IAP
    服务器知道。所以,我们绝不管收据里的数码,只要将出去怼给后台,后台还怼给苹果就是可以了。

  • 对此咱们付出给后台的收据,后台可能会见召开过的符号。但是后台要认清当前底此收据是否之前早已上污染了了,这时我们好开一个
    MD5,我们将 MD5 的结果一块上传给服务器。

  • 种里做了累累报警的拍卖,比方说咱们将收据存到 keyChain
    中,存储完成以后,要做一样不成检查,检查这个数量确实是怀上了,如果没,那这该报警,并拿报警音上传到我们的服务器,以防出现意外。又使说,IAP
    通知我们交易成功,我们便会见去取得收据,如果这收据为空,那纯属有题目了,此时理应报警,并拿报警音上传(项目里早已针对性这种状况展开了容错)。还有按照某笔交易认证了几十不良,还是不许证实,那这应该设定一个证明次数的告警阈值,比方说十不善,如果超过十糟糕就报警。

  • 于持久化到 keyChain 时,数据是绑定用户 userid
    的,这或多或少也是生死攸关,要不然会并发 A 用户的交易以 B 用户那里证实。

  • 对于已经失败过的认证请求,每半浅呼吁中的时光增长率也是应考虑的。这里用的比较简单的法,只要是曾经同后台验证了同时失败了的交易,
    两不好呼吁中的日间隔是
    失败的次数 * BLPaymentVerifyUploadReceiptDataIntervalDelta。同时也对步长的顶特别价值做了限,防止步长越来越不行,用户体验差。

  • 再有部分细节,下面两独艺术肯定要以遵循要求调用,否则后果很要紧。下面的次只法子,如果用户已等录,重新起动的时刻吧使调用一软。

/**
 * 注销当前支付管理者.
 *
 * @warning ⚠️ 在用户退出登录时调用.
 */
- (void)logoutPaymentManager;

/**
 * 开始支付事务监听, 并且开始支付凭证验证队列.
 *
 * @warning ⚠️ 请在用户登录时和用户重新启动 APP 时调用.
 *
 * @param userid 用户 ID.
 */
- (void)startTransactionObservingAndPaymentTransactionVerifingWithUserID:(NSString *)userid;
  • 再有一个问题,如果用户眼前还出无获取验证的贸易,那么这异脱离登录,我们应当叫个
    UI 上之提示。通过下这个措施去用用户眼前是否生非落验证的市。

/**
 * 是否所有的待验证任务都完成了.
 *
 * @warning error ⚠️ 退出前的警告信息(比如用户有尚未得到验证的订单).
 */
- (BOOL)didNeedVerifyQueueClearedForCurrentUser;
  • 再有对于开发是串行还是并行的挑选。串行的意思是只要用户眼前产生非得的贸易,那么即使无允开展选购。并行的意是,当前用户发生免形成的贸易,仍然可开展购买。我提供的源码是支撑彼此的,因为当时筹之上即便考虑到这个题材了。事实上,苹果对同一个交易标识的产品的贾是串行的,就是公眼前产生非给付成功的货
    A,当你再度买入是商品 A
    的当儿,是休能够请成功之。我们最终兼顾后台的逻辑,为了让后台同事更加便民,我们以了串行的艺术。采用串行就会见带一个逻辑漏洞就是,假如有用户他买之后出现异常,导致无法以正规的法子充钱并且
    finish
    某笔交易,最后经和我们客服联系的章程手动充钱,那么他的钥匙链就直发平等画不到位的贸易,由于我们的购买时串行的,这样见面导致这个用户还为迫于购买活。这种情景吧是得警惕的,此时单待跟后端同时约定一下,再次验证这笔订单的时光回来一个错误码,把这笔订单特别的
    finish 掉就吓了。

  • 再有一个 IAP 的 bug,就是 IAP
    通知交易形成,然后我们把贸易数额存起来去后台验证,验证成功之后,回到
    APP 使用 transactionIndetify 从 IAP
    未到位交易列表中取出对应的交易,将即刻比较交易 finish 掉,当 IAP 出现
    bug
    的下,这个市找不交,整个未成功交易列表都为空。而且复现也不行简短,只要在弱网下交易成功就杀掉
    APP
    就好复现。所以我们要答应针对之题目。应对之方针就是吃咱们囤的数额加以一个状态,一旦出现验证成功返回
    finish 的下找不至对应之贸易,就先行被存储数据加以一个
    flag,标识这笔订单就说明了了,只是还无找到相应之 IAP 交易进行
    finish,所以后来每次从未说明交易里得到多少的时,都要将出此
    flag 的交易对比一下,如果起就说明了的市,就一直拿那无异笔画交易
    finish 掉。

08.还澳门新萄京发出怎样问题?

至现行竣工,第一首上提及的八只问题,有七只当这无异首文章被都有对应之缓解方案。由于篇幅由,我就是不慌段大段的贴代码了,具体执行,肯定要扣源码的,并且我写了巨细无比之注释,保证每个人还能够看懂。

唯独的确就是无问题了呢?不是的,现在都领略的问题还有零星只。

  • 没验证完, 用户更换了 APP ID, 导致 keychain 被更改。
  • 订单没有以到收据, 此时用户更换了手机, 那么这收据肯定是拿不至之。
  • ……

首先单问题,看起而鸡蛋放在两只篮子里,比方说,数据如果以持久化到
keyChain
和沙盒中。但是这次没有召开,接下去看景,如果真的发这种问题,可能会见这样做。

亚个问题,是苹果 IAP
设计上的一个万分之弱点,看似无解,出现这种情形,也就是用户千方百计使拦交易得逞,那只能他把苹果的订单邮件发给我们,我们手动于他加钱。

另还有问题吧,请各位在评论区补充,一起谈论,谢谢君的读书!!

本身之文章集合

脚是链接是自身有文章的一个集目录。这些文章是涉及实现之,每篇文章被还出
Github
地址,Github
上还发生源码。

本人之章集合索引

汝还足以关心本身好维护的简书专题 iOS开发心得。这个专题的稿子都是真心实意的干货。如果您产生问题,除了在篇章最后留言,还好当微博 @盼盼_HKbuy达受我留言,以及走访我的 Github。

发表评论

电子邮件地址不会被公开。 必填项已用*标注