iOS 支付流程和掉单处理
IAP支付流程
- 用户进入购买虚拟物品页面,App从后台服务器获取产品列表然后显示给用户
- 用户点击购买购买某一个虚拟物品,APP就发送该虚拟物品的productionIdentifier到Apple服务器
- Apple服务器根据APP发送过来的productionIdentifier返回相应的物品的信息(描述,价格等)
- 用户点击确认键购买该物品,购买请求发送到Apple服务器
- Apple服务器完成购买后,返回用户一个完成购买的凭证
- APP发送这个凭证到后台服务器验证
- 后台服务器把这个凭证发送到Apple验证,Apple返回一个字段给后台服务器表明该凭证是否有效
- 后台服务器把验证结果在发送到APP,APP根据验证结果做相应的处理(通知apple服务器交易完成)
具体流程
- PayApple实现SKPaymentTransactionObserver和SKProductsRequestDelegate接口
- app启动完成didFinishLaunching的时候添加支付事务监听,有以前未结束的支付启动设置监听的时候也会回调步骤6
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
- 支付发起先根据购买商品id查询商品SKPayment信息,查询完成回调productsRequest(SKProductsRequestDelegate接口)
-(void) doPay:(NSString*) param
{
NSString* productId = @"test001";
SKProductsRequest *productRequest = [[SKProductsRequest alloc]initWithProductIdentifiers:[NSSet setWithObject:productId]];
productRequest.delegate = self;
[productRequest start];
}
- 查询回调productsRequest,发起支付(创建SKPayment 加入SKPaymentQueue)
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *myProduct = response.products;
if (myProduct.count == 0) {
// NSLog(@"无法获取产品信息,购买失败,支付中断");
return;
}
SKPayment * payment = [SKPayment paymentWithProduct:myProduct[0]];
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
- 支付回调updatedTransactions,根据不同状态处理,有以前未结束的支付启动设置监听的时候也会回调这里
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction *transaction in transactions)
{
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchased://付款完成
// NSLog(@"transactionIdentifier = %@", transaction.transactionIdentifier);
[dictPayTransaction setObject:transaction forKey:transaction.transactionIdentifier]; //保存transaction 后面结束交易时候取
[self completeTransaction:transaction]; //交易凭证发给后台服务端,后台服务端发送给apple服务器验证
break;
case SKPaymentTransactionStateFailed://交易失败
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored: //已经购买过该商品
[self restoreTransaction:transaction];
break;
case SKPaymentTransactionStatePurchasing: //支付进程开始
// NSLog(@"SKPaymentTransactionStatePurchasing");
break;
default:
break;
}
}
}
- 后台服务端发送给apple服务器验证以后返回,结束本次交易
-(void) finishPay:(NSString*) param
{
NSString* transId = param;
SKPaymentTransaction* payTransaction = [dictPayTransaction valueForKey:transId];
if(payTransaction)
{
[[SKPaymentQueue defaultQueue] finishTransaction: payTransaction];
[self saveOrderInfo];
}
else
{
NSLog(@"finishPay find trans err");
}
}
掉单处理
掉单出现情况
- IAP支付流程中第5个步骤,apple支付完成,没有返回(比如:支付过程中app异常崩溃),这里在下一次app启动执行添加支付监听的时候会回调updatedTransactions继续支付流程(不过这里以前的支付信息就没了)
- IAP支付流程中第6,7,8个步骤,由于网络等问题都可能异常导致支付流程中断
掉单处理
- 支付发起前先请求服务端生成订单号order,在支付回调updatedTransactions中支付完成时候先建立transaction.transactionIdentifier与order的映射关系(可以使用dicnionary等)并保存本地磁盘。
- 每次启动时候先恢复存储的transaction.transactionIdentifier与order映射关系。
- IAP支付流程中6,支付凭证发送给服务端的是带着订单号一起,这样服务器可以根据订单号查询支付的真正用户(app 重启以后用户有可能已经改变)
- IAP支付流程中第8通知apple服务器交易结束的时候同时删除该条transaction.transactionIdentifier与order映射关系
掉单说明
- 由于apple没有提供附加字段在交易过程中保存自己创建的订单号,然后支付流程是异步的,追踪是具体哪一笔支付比较困难,掉单处理起来比较麻烦
- payment.applicationUsername这个不能填入订单号,不稳定有时候会没有值,apple官方也有说明这个不是做这个用的,新版api好像是readonly没法用
- 上面掉单处理理论上还是存在问题,IAP支付流程中第5个步骤出现问题,重启app恢复支付流程的时候如果用户更换了没办法追踪到上一个用户,不过这个情况出现概率应该特别少了。
- 有支付未完成的时候,app卸载重装也可能会丢失订单号与交易的映射关系,再切换用户就会出现问题。
其它情况处理
- 支付过程是异步的,支付transaction.transactionIdentifier也不是一开始就有的,所以在发起支付到updatedTransactions生成transactionIdentifier之间也可能出现串订单号问题(比如连续点击付款),我们的处理方式是,发起支付的时候缓存订单号,建立transactionIdentifier与order的时候删除order缓存。有缓存order的一段时间(10s)内不再发起第二笔支付。
- app重启恢复订单回调,这时候app用户可能还未登录或者与服务器的连接还没建立。无法将支付凭证发送给服务端,我们这里做了个缓存,app准备好以后再把缓存的支付信息发送给服务器