[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 上的经过。

毫不顾虑,我没有会只说原理不留源码,我既用我司的源码整理出来,你用时只有需要甩到工程中不怕可以了,下面开始我们的情节

并非操心,我从没会只称原理不养源码,我就拿我司的源码整理出来,你采取时独自待甩到工程被即好了,下面开始我们的情节

源码在此地。

源码在此间。

直达同样篇之辨析了 IAP
存在的题材,有九独点。如果您不知情是啊九单点,建议乃先失押一下齐平等首文章。现在咱们根据达同首总结的题材一个一个来对号入座解决。

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

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

01.题外话

现年上半年的民众号打赏事件,大家可还记得?我们本着苹果强收过路费的一言一行愤懑,也为微信可惜不已,此事最终以腾讯高管团队访问苹果打上句号。显然,协商结果个别位老板跟他们的组织还特别中意。

01.越狱的问题

至于越狱导致的题材,总是充满了不明确,每个人都非同等,但是还是面临了攻击致的。所以,我们应用的办法简单粗暴,越狱用户一律免容许利用
IAP
服务。这里自己吧建议您这么做。我之源码中起一个家伙类用来检测用户是否越狱,类名是
BLJailbreakDetectTool,里面仅来一个办法:

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

使您切莫思利用自己包的计,也堪行使友盟统计里产生一个术,如果你的类衔接了友盟统计,你
#import <UMMobClick/MobClick.h> ,里面有只近乎措施:

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

02.熟悉的支付宝与微信支付

细看一下底就张图,这是咱每次在选购早餐使用支付宝支付的流程图。下面我们来同样步一步看一下各级一样步对应之操作原理。

第一步:我们的 APP
发起一画开支交易,此时,第一件事,我们若去我们温馨的服务器上创办一个订单信息。同时服务器会组装好同一笔交易交给我们。关于组建交易信息,有零星栽做法,第一种植就是是支付宝推荐我们做的,由咱们服务器来组装交易信息,服务器加密交易信息,并保留签名信;另一样种做法是,服务器返回商品信息给
APP,由 APP
来组装交易信息,并进行加密处理等操作。显然我们应当利用第一栽艺术。
第二步:服务器创建好市信息之后,返回给 APP,APP
不对交易信息做处理。
第三步:APP 拿到市信息,开始调整起支付宝的 SDK,支付宝的 SDK
把贸易信息污染为支付宝的服务器。
第四步:验证通过下,支付宝服务器会报支付宝 SDK 验证通过。
第五步:验证通过后,我们的 APP 会调起支付宝 APP,跳反至支付宝
APP。
第六步:在出宝 APP
里,用户输入密码进行交易,和支付宝服务器进行通讯。
第七步:支付成功,支付宝服务器回调支付宝 APP。
第八步:支付宝回到我们团结之 APP,并透过
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
方法处理支付宝的回调结果,对应之拓刷新 UI 等操作。
第九步:支付宝服务器会回调我们的服务器并把收据传被咱服务器,如果我们的服务器并未确认就接到支付宝的收据信息,那么支付宝服务器就会见一直回调我们的服务器,只是回调时间距离会愈老。
第十步:我们的服务器收到支付宝的回调,并回调支付宝,确认就吸纳收据信息,此时早餐买完了。

支付宝的出流程讲了了,那微信支付呢道得了了,因为它流程相似。

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 中。

呼吁记住这数目归档的层级,要不然,实现公文里看起有点傻。

03.坑爹的 IAP 支付

IAP 坑爹的远在起以下简单独面来了解。

第一方面,APP 不接 IAP 审核不受了。接不接
IAP,苹果不是同而商量,而是强制要求,爸爸说怎么样,就怎样。当然,这首文章解决不了这个问题,所以呢只是说说而已。上面说了微信公众号的事情,虽然她不是
IAP 的事体,但是精神上还属强收过路费的一言一行。

仲方面,坑开发人员。下面开始频繁坑。

特来 8 步,比出宝少 2 步,对怪?看起比支付宝还简要,有木有?

第一步:用户开始置办,首先会见去我们团结一心之服务器创建一个交易订单,返回给
APP。
第二步:APP 拿到交易信息,然后开始调整起 IAP
服务创建订单,并将订单推入支付队列。
第三步:IAP 会和 IAP 服务器通讯,让用户确认市,输入密码。
第四步:IAP 服务器回调 APP,通知采购成功,并将收据写入到 APP
沙盒中。
第五步:此时,APP 应该去抱沙盒中的收据信息(一段 Base 64
编码的多少),并以收据信息达传于服务器。
第六步:服务器将到收据后,就应当去 IAP
服务器询问者收据对应的既会的订单号。
第七步:我们团结之服务器将到这收据对应的已会的订单号之后,就失校验当前的就会订单被是不是出要询问的那无异画,如果生,就报
APP。
第八步:APP 拿到查询结果,然后拿这笔交易受 finish 掉。

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;
}

04.比照支付宝和 IAP

没有啥异常疾病,对吧?现在来详细分析一下。

由活动端所处之网络环境远远比服务端要复杂,所以,最充分可能出现问题的凡同移动端的通讯及。对于支付宝,只要移动端确实会成功,那么接下的辨证工作还是服务器被服务器之间的通讯。这样一来,只要用户真正来了同样画交易,那么接下的证实就易得可靠的基本上,而且支付宝服务器会一直回调我们的服务器,交易的可靠性得到了特大的担保。

无异于,我们又来瞧
IAP,交易是同样的。但是证实交易眼看同围绕需要活动端来教我们自己之服务器来拓展查询,这是首先个坑,先记一笔。另外一些,IAP
的服务器远在美国,我们的服务器去询问延时相当严重,这是该

04.制止入新市

方说明队列里本身还有压入情景没有讲,压入观有三种情况。

首先种是出现意外,就是初始化的时段,如果出现用户刚好交易截止,但是 IAP
没有通我们交易完成的动静,那么这更夺 IAP
的贸易队列里检查一总体,如果生没有出吃持久化到 keyChain 的,就直接抑制入
keyChain 中开展持久化,一旦上 keyChain
中,那么这笔交易就可知为正确处理,这种情况于测试环境下经常出现。

老二种植是正规贸易,IAP 通知交易形成,此时将交易数额压入 keyChain 中。

老三种与第一栽恍若,用户从后台进入前台的下,也会失掉反省一举沙盒中发出无发生无发生持久化的贸易,一旦产生,就拿这些交易压入
keyChain 中。

点三独压入情景,能顶深程度达到保证我们的持久化数据能与用户真正的交易并,从而防范苹果出现交易得逞也绝非通知我们如果导致的
bug。

05.IAP 设计及的坑

点说了片只很非常的坑,接下去看一样拘禁 IAP 本身有哪坑。最老的一个即是,从
IAP 交易结果出来到通报 APP,只发生同一坏。这里出以下几个问题:

1.设用户后请成后,网络就坏了,那么苹果的 IAP
也结束不顶开成功之打招呼,就无奈通知 APP,我们为没法让用户发货。
2.比方 IAP 通知我们开成功,我们叫服务器去 IAP
服务器查询失败以来,那即便如当下次 APP
启动之时,才见面另行通知我们来无认证的订单。这个周期从没法想象,如果用户一个月无重开
APP,那么我们或一个月没法让用户发货。
3.有人报告,IAP
通知曾交易得逞了,此时去沙盒里拿走收据数据,发现也空,或者出现通知交易成功那笔交易从不吃马上的状副到沙盒数据被,导致我们服务器去
IAP 服务器询问的早晚,查不交这笔订单。
4.如果用户的交易还并未到手验证,就把 APP
给卸载了,以后要怎么过来那些没有于验证的订单?
5.越狱手机来那么些奇葩的收据丢失或无效或叫调换的问题,应该如何酌情处理?
6.市从不发生变化,仅仅是重新开一下,收据信息就是会见发转移。
7.当证实交易得逞之后我们错过取 IAP
的消验证交易列表的当儿,这个列表没有数量。

吓吧,算起来有九单比较特别之题材了,还发生没有看到的呼吁各位补充。这九个问题,基本上每一个还是沉重之。这么多之不确定性,我们相应怎么概括处理,怎么相互平衡?

咱们事先放开平加大这些题材,下同样首就一头来下手解决这些题目,现在咱们事先来拘禁无异圈
IAP 支付的核心代码。

05.色组织总结

交现行结,我们的结构已经起矣大约了,现在咱们来总一下咱们现底型布局。

BLPaymentManager 是交易管理者,负责同 IAP
通讯,包括商品查询和请功能,也是市状态的监听者,对接沙盒中收据数据的落和创新,是咱凡事支付的入口。它是一个单例,我们的证明队列是挂于她身上的。每当发生新的市进入的下(不管是什么状况进来的),它还见面把这笔交易丢给
BLPaymentVerifyManager,让 BLPaymentVerifyManager
负责夺证明这笔交易是否中。最后,BLPaymentVerifyManager 也会和
BLPaymentManager 通讯,告诉 BLPaymentManager 某笔交易的状态,让
BLPaymentManager 处理掉指定的贸易。

BLPaymentVerifyManager
是印证交易队列管理者,它其中发生一个欲征的交易 task
队列,它负责管理这些队列的状态,并且让这些任务之推行,保证每笔交易证的顺序循序。它的里有一个
keyChain,它的队列中的职责都是自从 keyChain
中初始化过来的。同时其呢管理在keyChain 中之数,对keyChain
进行增删改查等操作,维护keyChain 的状态。同时为和 BLPaymentManager
通讯,更新交易的状态(finish 某笔交易)。

keyChain
不用说了,负责交易数额的持久化,提供增删改查等接口给她的企业管理者使用。

BLPaymentVerifyTask 负责同服务器通讯,并且用报道结果回调出来吃
BLPaymentVerifyManager,驱动下一个认证操作。

06.IAP 支付代码

咱事先不错过思那么多,先把开发逻辑跑通再说。下面我们省 IAP 的代码。

#import <StoreKit/StoreKit.h>

@interface BLPaymentManager ()<SKPaymentTransactionObserver, SKProductsRequestDelegate>

@end

@implementation BLPaymentManager

- (void)dealloc {
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}

- (void)init {
    self = [super init];
    if(self) {
         [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    }
    return self;
}

- (void)buyProduction {
    if ([SKPaymentQueue canMakePayments]) {

        [self getProductInfo:nil];

    } else {
        NSLog(@"用户禁止应用内付费购买");
    }
}

// 从Apple查询用户点击购买的产品的信息.
- (void)getProductInfo:(NSString *)productIdentifier {
    NSSet *identifiers = [NSSet setWithObject:productIdentifier];
    SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:identifiers];
    request.delegate = self;
    [request start];
}


#pragma mark - SKPaymentTransactionObserver

// 购买操作后的回调.
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
    // 这里的事务包含之前没有完成的.
    for (SKPaymentTransaction *transcation in transactions) {
        switch (transcation.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                [self transcationPurchasing:transcation];
                break;

            case SKPaymentTransactionStatePurchased:
                [self transcationPurchased:transcation];
                break;

            case SKPaymentTransactionStateFailed:
                [self transcationFailed:transcation];
                break;

            case SKPaymentTransactionStateRestored:
                [self transcationRestored:transcation];
                break;

            case SKPaymentTransactionStateDeferred:
                [self transcationDeferred:transcation];
                break;
        }
    }
}


#pragma mark - TranscationState

// 交易中.
- (void)transcationPurchasing:(SKPaymentTransaction *)transcation {
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
    if (!receipt) {
        NSLog(@"没有收据, 处理异常");
        return;
    }

    // 存储到本地先.
    // 发送到服务器, 等待验证结果.
    [[SKPaymentQueue defaultQueue] finishTransaction:transcation];
}

// 交易成功.
- (void)transcationPurchased:(SKPaymentTransaction *)transcation {

}

// 交易失败.
- (void)transcationFailed:(SKPaymentTransaction *)transcation {

}

// 已经购买过该商品.
- (void)transcationRestored:(SKPaymentTransaction *)transcation {

}

// 交易延期.
- (void)transcationDeferred:(SKPaymentTransaction *)transcation {

}


#pragma mark - SKProductsRequestDelegate

// 查询成功后的回调.
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    NSArray<SKProduct *> *products = response.products;
    if (!products.count) {
        NSLog(@"没有正在出售的商品");
        return;
    }

    SKPayment *payment = [SKPayment paymentWithProduct:products.firstObject];
    [[SKPaymentQueue defaultQueue] addPayment:payment];
}

@end

代码大致做了之类事情,初始化的时失去丰富支付结果的监听,并于 -dealloc:
方法被移除监听。同时可以透过
- (void)fetchProductInfoWithProductIdentifiers:(NSSet<NSString *> *)productIdentifiers
方法查询后台配置的商品信息。通过 -buyProduction:
方法购买活,购买成功之后,IAP 通过
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
方法通知采购进度。

06.收条不同台处理

生同行报告说,IAPbug,这个 bug
就是不言而喻通知交易都打响了,但是去沙盒中得到收据时,发现收据为空,这个题材吗是要是切实回复之。

今昔举行了以下的处理,每次跟后台通讯的结果归为三类,第一类似,收据有效,验证通过;第二类,收据无效,验证失败;第三好像,发生错误,需要再次验证。每个
task 回来都是只出或是当下三栽情景的等同种植,然后 task
的回调会让班管理者,队列管理者会拿回调传下给市管理者,此时贸易管理者在下面的代理方被创新最新的收据,并拿新收据还传给班管理者,队列管理者下次发起呼吁虽是利用最新的收据进行说明操作。

@protocol BLPaymentVerifyTaskDelegate<NSObject>

@required

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

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

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

@end

自身的篇章集合

脚是链接是自具备文章的一个汇聚目录。这些文章是涉及实现之,每篇文章中都发出
Github
地址,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 掉。

若还足以关注本身自己维护的简书专题 iOS开发心得。这个专题的章都是实在的干货。如果你产生题目,除了当篇章最后留言,还得在微博 @盼盼_HKbuy达于我留言,以及走访我的 Github。

08.还发生哪问题?

及今为止,第一篇上提及的八个问题,有七单以当下无异于篇稿子中还生相应的解决方案。由于篇幅由,我就未怪截大段的贴代码了,具体实行,肯定使扣押源码的,并且自己形容了巨细无比的注解,保证每个人且能看懂。

然真正就从未问题了邪?不是的,现在既掌握之题材还有零星单。

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

先是个问题,看起如鸡蛋在两单篮子里,比方说,数据要又持久化到
keyChain
和沙盒中。但是这次没举行,接下去看情形,如果真的有这种问题,可能会见这样做。

亚只问题,是苹果 IAP
设计上的一个可怜之先天不足,看似无解,出现这种情景,也就是用户千方百计使阻止交易得逞,那只能他管苹果的订单邮件发给我们,我们手动于他加钱。

其余还有问题来说,请各位在评论区补充,一起谈论,谢谢您的读!!

本身的文章集合

下这个链接是我有所文章的一个集目录。这些章是涉及实现的,每篇文章被都产生
Github
地址,Github
上都发出源码。

自身的篇章集合索引

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

相关文章