一个完整的iOS购买交易流程图:
公司的项目主要为游戏,游戏工程对接iOS支付时主要使用sdk的方式,所以在上图中标注了『sdk服务端』,『sdk客户端』,『游戏服务器』和『游戏客户端』,如果项目为app,则只用app客户端和服务端即可,向服务端获知订单合法性和确认发放道具均由客户端执行。
游戏项目都会用到苹果内购IAP(In App Purchases)的功能,整理了下自己的相关代码,还有在iap接入游戏过程中遇到过的一些问题,在这里分享一下。
公司内部有商务平台人员负责开发者账号相关功能,如何去创建App ID和product ID,就不叙述了,主要强调下product ID的类型。
product ID可以选择三种类型,
non-consumable(购买一次,永久使用,非消耗品)
comsumable(多次购买,消耗品)
subscription(自动续款,订阅。目前多适用于杂志或者新闻类型的app,包含自动续费和非消耗性续费,如月卡等)。
目前常用到的是non-consumable和comsumable两种类型,主要区别是是non-consumable只能购买一次,comsumable可以无限制购买。如果游戏中有non-consumable这种类型的商品,需要在游戏中提供一个restore按钮,来实现”恢复购买”功能,否则应用审核会被拒(亲身经验),如果只有comsumable类型则没有这个需求。
在开始调用IAP接口之前需要对xcode工程进行一些配置,首先你要确定的是项目的bundle Identifier是正确的,与你的appid(支持IAP)相对应,然后需要在项目中添加 StoreKit framework。
下面为代码的具体执行:
首先在.h文件中导入头文件和遵守协议,声明相关成员变量和block
监听用户支付的结果。如果没有这句代码的话,那么 paymentQueue:updatedTransactions 这个回调将不会被执行,无法监听获取用户支付结果信息
用户点击购买按钮,游戏需要传入对应商品的productID(此处可block回调表示最终的购买结果)
在这个方法里,建议使用Reachability文件或者系统检测方法做一下网络判断,有网的情况下,查询用户是否允许应用内付费 “[SKPaymentQueue canMakePayments]”,如果可以则继续
如果用户允许购买,可以继续执行下一步(_productID是声明的全局变量,用来保持游戏传入的productID),声明一个临时集合,包含_productID的数组,去创建SKProductsRequest请求,此请求用来查询_productID是否存在之前已经在itunes connect配置完的productID列表中
开始查询后,如果SKProductsRequest请求成功就会进入对应的成功回调。
在回调里可以获取对应productID的相关信息,如果“获取产品信息失败”则直接return,如果获取成功,需要去触发购买请求,创建一个新的SKPayment对象,并且把这个对象加载到队列中去。此时购买请求的API将被触发,系统会自动弹出提示信息,类似“你确定购买xxx产品吗?需要支付多少金额”
SKProductsRequestDelegate中并没有给出查询请求失败的回调函数,不过在SKProductsRequest的父类SKRequest发现有一个requestFailedWithError的协议方法,亲测后发现是可以当作查询失败的回调来调用的(还有一个requestDidFinish成功的协议方法,不过没有使用过)
在这个requestFailedWithError的方法里做了相关操作
接下来便是用户的支付操作。
当支付完成,取消或者失败的时候,将会执行paymentQueue:updatedTransactions回调
SKPaymentTransactionStatePurchased,交易完成/成功,进入完成交易的自定义处理方法
SKPaymentTransactionStateFailed,交易失败/取消,进入交易失败的自定义处理方法
需要注意的是在用户支付结束后(无论成功和失败)都必须执行
“ [[SKPaymentQueue defaultQueue] finishTransaction: transaction];”,
苹果会从队列中删除已经完成的交易的记录,否则下次点击支付会提示『该商品已经购买,需要恢复』 。
注:由于苹果支付的特殊性,支付的结果信息全部只能从客户端获取,如果客户端被外挂破解(越狱设备容易出现),模拟客户端发向苹果的请求,最终导致游戏方收到成功的通知,便会给游戏方造成极大的损失。所以在支付成功后,客户端可以获取交易的收据transaction.transactionReceipt(如下图),将此收据base64加密后和其他相关信息发送给自己的服务器,然后由服务端向苹果方面验证收据的合法性,如果收据合法,服务端可通知游戏方交易成功,也可以通知客户端进行相关操作(取消Loading动画,删除本地保存的收据信息等)。这样便可保证交易的正确性。
(以下向苹果验证的行为可交予服务端执行)
向苹果验证的地址:
appStore上线:https://buy.itunes.apple.com/verifyReceipt
沙盒测试: https://sandbox.itunes.apple.com/verifyReceipt
请求json数据:单键值对{"receipt-data" : "base64编码后的数据"}
苹果返回json数据App Store的返回值也是一个JSON格式的对象,包含两个键值对, status和receipt:
{
status” : 0,
receipt” : { … }
}
status的值为0,则表示交易合法,收据有效,否则无效
receipt中对应的内容:
为避免用户在支付过程中清除游戏进程,或者网络中断而出现支付结果丢失,最终导致用户已付费但游戏并没有发放道具的情况,所以在获取用户订单orderid和苹果回调的receipt后,将其存储在本地(我选择的是userdefaults存储),如果出现上述支付问题的情况,在用户再次登录游戏或者重新点击支付时判断本地存储信息是否同时存在订单orderid和receipt,如果同时存在,则去执行下图
分析:
如果同时存在,会出现两种可能:
1. 客户端向服务端发送包含recipt请求失败
2. 服务端向客户端告知合法性时失败
但存在receipt可以肯定的是用户已完成扣费,苹果会记录用户付费状态,下次进入程序后执行[[SKPaymentQueue defaultQueue] addTransactionObserver:self];注册监听者,苹果会主动执行- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions; 根据上一次交易的状态执行对应的方法。
update采用的block,在[self completeTransaction:transaction]里获取receipt后进行回调
恢复购买:
还有第三种需要自己去处理的交易结果“SKPaymentTransactionStateRestored,恢复已经购买过商品”,单独说明一下。
在文章开始已经强调过,只有使用productID类型存在non-consumable的类型时需要添加一个“restore”按钮来执行恢复购买功能。
点击“restore”按钮后只需要执行:
执行后,系统会自动检测,在当前用户完成交易的队列中是否存在已经有购买过产品的事件,如果有的话将会重新完成交易,进入-paymentQueue:updatedTransactions回调,如果没有则恢复失败,进入失败回调;
在-paymentQueue:updatedTransactions里“SKPaymentTransactionStateRestored”情况下
进行方法处理
如图:
对于需要恢复购买的productID,如果只有一个的话可以直接在此方法中直接block返回,如果存在多个,则声明一个可变数组
在此方法中addObject添加id,在-paymentQueue:updatedTransactions:(NSArray*)transactions;中的遍历结束后进行反馈
最后需要注意的是:
1.建议使用非越狱iOS设备进行IAP测试。越狱后的iOS设备,无法进行沙盒测试,可能会受到一些第三方插件的影响。
2.在测试之前先在iOS设备的设置中把当前appStore账号注销掉,非测试账号沙盒测试是无法查询到productID对应的产品信息。
3.不能同时进行多个交易,否则会因为苹果在未执行第一个交易的finishedTrancation时,收到第二个交易请求,最终结果会是第一个交易成功,而第二个交易会弹出『已经购买的』提示。
4.如果用户在苹果系统弹出的是否购买框中选择确认购买后,此时程序出现崩溃或者杀死程序进程,或者网络中断等极端情况下,会造成客户端无法获取苹果服务端反馈的receipt,最终出现用户已扣费,但道具未能发放的情况。目前没有较好的处理方法,只能采取线下验证用户交易,然后主动补发道具。
5.服务端需要执行严格的支付订单信息验证,来保证不出现被第三方工具刷单的情况。向苹果求证receipt的步骤建议交由服务端去做,客户端的数据安全性不值得信任。
以上,仅为个人见解,不够成熟,如各位发现问题,请随时指出,不尽感谢