最近项目中接入了ApplePay内购,发现部分用户出现漏单的情况。发现漏单的情况是因为部分用户支付完成之后立即退出了App,导致没有接收到小票信息,或者小票信息和后台订单没有对应上,出现了漏单情况。
查看系统API发现,applicationUsername可以传入参数,内部测试通过,但是在上线到AppleSotre的时候,发现用户支付完成后会闪退。最后通过查看闪退日志发现,问题的根源是因为SKPayment的applicationUsername属性内容为空,然而我们没有检验这个值,直接使用setObject:forKey:存为字典,导致闪退。
@property(nonatomic, copy, readwrite, nullable) NSString *applicationUsername
在这里,我犯了以下两个错误
错误一,Object对象不能为nil:
NSObject对象可以通过Runtime,使用setValue:forKey:方法来对一个对象进行KVC赋值。查看NSKeyVlaueCoding可以知道,key对象必须是NSString类型,value是ObjectType类型;如果value为nil,则相当于清空key对应的值
@interface NSMutableDictionary<KeyType, ObjectType>(NSKeyValueCoding)
- (void)setValue:(nullable ObjectType)value forKey:(NSString *)key;
@end
在NSMutableDictionary类中,提供了另一个设置“键-值”的方法
- (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey;
此方法中,key是遵循NSCopying协议,即遵循copyWithZone:的任意对象类型;value是任意对象类型值,注意不能为nil
错误二:
不论是通过测试证书还是TestFlight安装的ipa包,内购都是使用的沙盒测试,即sandbox环境,而正式的AppleStore环境。恰恰正式环境中,applicationUsername属性值是为空的。
针对此问题,我们做了如下修改:
1、放弃使用applicationUsername来进行赋值,在SKProductsRequestDelegate的代理方法,校验信息无误之后,将自己的订单信息保存到沙盒中
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response API_AVAILABLE(ios(3.0), macos(10.7))
2、在SKPaymentTransactionObserver代理回调中,根据transactionState的状态做处理。如果是SKPaymentTransactionStateFailed、SKPaymentTransactionStateDeferred则将刚才保存的订单信息移除,否则等待支付完成,像服务器校验订单通过之后再移除沙盒中对应存入的订单。
- (void)paymentQueue:(SKPaymentQueue*)queue updatedTransactions:(NSArray *)transactions
3、App启动时就全局监听支付Apple发的支付结果,即
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
如果有接收到苹果返回的漏单小票,则将小票和本地沙盒中存储的订单一起提交给服务器。如果没有,则将沙盒中储的订单提交给服务器。
4、沙盒中存储的订单列表,重新开始统计支付订单内容
5、后台根据订单、用户信息、小票确认信息,如果没有订单信息,则根据小票返回的商品信息,发放给玩家对应的商品内容