目前游戏里面出售的道具,都属于consumable products(消耗性商品),下面的说明流程都是针对这类商品的。别的类型商品处理方法,不完全一样。
app启动的时候即注册支付队列消息的回调者,这个回调者应该是一个单体类,保证在app整个生命周期都存在。ios会回调之前未处理完成的订单。
- (id)init {
if((self = [super init])) {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
加载商品是去app store服务器查询,然后等待apple回调,会比较慢。为了避免启动时商店页面空白,可以客户端先把所有商品的名字价格这类信息预先存好(或从服务器查询到),在加载完成前,显示成灰色且不可购买状态。
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
发起支付之后,ios会在适当的时机回调之前注册的支付回调函数。回调收到的是一个票据(transaction)数组,需要遍历处理每一个票据。 票据共有五组可能的状态,对于消耗性商品需要关注其中的4个,但是仅有两个比较有意义:
-
SKPaymentTransactionStatePurchasing
apple store正在处理;如果不想重复购买,此时可以锁定商品界面
-
SKPaymentTransactionStatePurchased
用户已经付费;需要重点处理
-
SKPaymentTransactionStateFailed
处理失败,显示错误
-
SKPaymentTransactionStateRestored
不需要关注
-
SKPaymentTransactionStateDeferred
等待用户输入支付密码之类的,好像也不需要单独处理
对于SKPaymentTransactionStatePurchased状态的订单,还需要两步:给玩家商品和关闭订单。关闭订单只需要:
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
麻烦的是给玩家商品的时机,可以有不止一种方案的。
有两种选择:
- 关闭订单前给玩家商品
- 关闭订单后再给玩家商品
选择1的风险是,给了商品之后,玩家通过某种手段不让你关闭订单,然后找苹果退钱(不知道是否可行,也不知道苹果给不给退,是我的猜测)。 选择2的风险是,可能掉单。
如果选择1,流程很简单:
- 发送票据给服务器,服务器校验成功后给予玩家商品,并通知客户端
- 客户端关闭订单 在2之前,每次重启app,苹果都会重发这个订单,所以肯定可以确保1完成,不会掉单。服务器需要确保同一个订单只给一次商品,应该也容易实现。
如果选择2,流程较为复杂:
- 保存票据到服务器
- 关闭订单
- 通知服务器处理订单
- 服务器校验订单,并给予玩家商品
如果在2之后,客户端异常退出,可能就会形成掉单;需要手工确认,或者客户端每次登陆时,不管有没有订单,在成功加载商品后(确保和苹果通信正常),都执行3来避免掉单。
- 发送票据到apple store校验
如果票据正确,会拿到解析后的票据数据。其中有个字段transactionIdentifier可以作为订单的唯一编号。 2. 确认订单未处理过
通过订单的唯一编号确认订单没有处理过。如果害怕长期来累积了太多的订单,可以结合票据里面的购买日期字段,来减少数据库压力。 比如任何超过一个月的票据,都认为是失败的。这样数据库中只需要存最近一个月处理过的票据即可。 3. 给玩家商品(略)
iap:
票据校验:
陌陌争霸客户端里面,有两个文件IAPManager.m
, IAPManager.h
, 是陌陌争霸的实现。采用的是先给商品后确认关闭,不推荐这种做法,供参考。