iOS开发:苹果内购组件

苹果内购代码组件的重构。

在iOS众多系统框架中,StoreKit算是被诟病得够多的了。StoreKit的设计,要求App来确保交易的最后交付——从苹果的生态来讲,这是可以理解的;但是交易流程中的错误,却很少返回有意义的错误码,“无法连接到iTunes Connect”成了无法明确的错误描述。

本文主要是组件代码重构的一个说明。

前置知识

内购的交互/交易流程如下图所示:

IAP相关的其他背景知识,不做具体介绍,

  • App内购买项目配置流程,请参考官网
  • 订单凭据校验,请参考官网
  • App内购买测试,请参考官网
  • 苹果的官方示例
  • 有疑问请参考FAQ

需要说明的几点是:

  • Transaction必须关闭,无论成功或失败。Transaction关闭后,App必须负责确保完成业务端的交易流程;
  • 需要考虑订单持久化问题;
  • 业务端的订单生成与校验逻辑;

任务

方案的设定,我们把每一次发起的交易作为一个任务,加入到任务队列,生命周期完成后移出队列。这样设计,一是可以保证各个任务(的处理逻辑)是独立的、生命周期是完整的,二是任务队列可以协调任务的并发情况。

任务的模型设计如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@interface HLTPaymentTask : NSOperation<HLTOrderConfiguration>
{
@protected
HLTOrderModel *_order;
HLTPaymentCompletion _completion;
NSTimeInterval _startTime;
}

// 任务状态
@property (nonatomic,assign) HLTTaskStatus taskStatus;;
// 事件代理
@property (nonatomic,weak) id<HLTPaymentTaskDelegate> delegate;
// 商品ID
@property (nonatomic,copy,readonly) NSString *productId;
// 订单信息
@property (nonatomic,strong,readonly,nullable) HLTOrderModel *order;
// 任务开始时间
@property (nonatomic,assign,readonly) NSTimeInterval startTime;
// 商品(查询到商品信息后可取)
@property (nonatomic,strong,readonly,nullable) SKProduct *skProduct;
// 订单生成器
@property (nonatomic,weak) id<HLTOrderGenerator> orderGenerator;
// 订单校验器
@property (nonatomic,weak) id<HLTOrderVerifier> orderVerifier;
// 用户自定义信息
@property (nonatomic,strong) NSDictionary *userInfo;
@property (nonatomic,strong) NSObject *userInfoObject;

- (instancetype)initWithProductId:(NSString *)productId completion:(HLTPaymentCompletion)completion;

@end

可以看到,Task维护了自身的taskStatus,它本身可以做状态流转的校验。

Task持有的订单模型HLTOrderModel,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@interface HLTOrderModel : NSObject <NSSecureCoding, HLTOrderConfiguration>

// 商品id
@property (nonatomic,copy) NSString *productId;
// 订单id
@property (nonatomic,copy) NSString *orderId;
// 用户标志
@property (nonatomic,copy) NSString *userIdentifier;
// 订单来源
@property (nonatomic,assign) HLTOrderSource orderSource;
// IAP交易状态
@property (nonatomic,assign) HLTOrderStatus orderStatus;
// 订单创建时间
@property (nonatomic,assign) NSTimeInterval createdTime;
// IAP交易开始时间
@property (nonatomic,assign) NSTimeInterval iapBeginTime;
// IAP交易成功时间
@property (nonatomic,assign) NSTimeInterval iapFinishTime;// todo iap transaction 超时
// 订单验证此次数
@property (nonatomic,assign) NSInteger receiptVerifyCount;
// 提示信息(预警等)
@property (nonatomic,copy) NSString *hint;
// 交易信息(用于备份)
@property (nonatomic,strong,readonly) HLTOrderTransaction *transaction;
// IAP交易信息
@property (nonatomic,strong,readonly) SKPaymentTransaction *skTransaction;
// 更新时间
@property (nonatomic,assign,readonly) NSTimeInterval updateTime;

// 用户自定义信息
@property (nonatomic,strong) NSDictionary *userInfo;
@property (nonatomic,strong) NSObject *userInfoObject;

- (instancetype)initWithProductId:(NSString *)productId;
- (BOOL)isEqualToOrder:(HLTOrderModel *)other;
- (void)updateWithTransaction:(HLTOrderTransaction *)transaction;
- (BOOL)isOrderIdValid;

@end

Order作为贯穿整个交易流程的状态集合,

① 是需要持久化到本地的;

② 可以携带业务定义数据,这个是必然的;

这两点都实现了。

业务逻辑注入

IAP内部的交易流程,业务方其实是不太关注的。业务方关注的是,它用这个组件的时候,如何根据业务定制“订单生成/验证”的逻辑,如何获取交易流程的状态回调。

① 状态回调,其实在Task的定义中可以看到,是提供了事件代理的。

② 业务定制,Task提供了orderGeneratororderVerifier作为业务逻辑注入的入口,实现接口就可以了;

外部入口

业务层看到的入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@interface HLTStoreKit : NSObject

// 订单生成器
@property (nonatomic,strong) id<HLTOrderGenerator> orderGenerator;
// 订单校验器
@property (nonatomic,strong) id<HLTOrderVerifier> orderVerifier;
// 持久化处理器
@property (nonatomic,strong) id<HLTOrderPersistence> orderPersistence;

+ (instancetype)defaultStore;
- (NSTimeInterval)launchTime;

#pragma mark - Configuration

/**
设置日志处理器
@param logger 日志处理器
*/
+ (void)setLogger:(void (^)(NSDictionary *params, NSString *format, ...))logger;

#pragma mark - Transaction

/**
开始/结束 监听IAP交易回调
@note 建议启动后开始监听
*/
- (void)startObservingTransaction;
- (void)stopObservingTransaction;

/**
购买商品
@note 错误信息请见`HLTPaymentErrorCode`

@param productId 商品ID
@param completion 完成回调
*/
- (void)purchase:(NSString *)productId configuration:(HLTOrderConfigurationBlock __nullable)configuration completion:(HLTPaymentCompletion __nullable)completion;

/**
重试订单(主要是verify)
@param order 订单
*/
- (void)tryRetrievalOrder:(HLTOrderModel *)order;

@end

继续完善。

Author: Jason

Permalink: http://blog.knpc21.com/ios/ios-lib-storekit/

文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。

Comments