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

相关文章

发表评论

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