iOS内购-收据验证以及漏单情况的处理
一.先说下验证方式
iOS 内购支付两种模式: 1、内置模式 2、服务器模式
1、内置模式的流程
内置模式的流程:
1.app从app store 获取产品信息
2.用户选择需要购买的产品
3.app发送支付请求到app store
4.app store 处理支付请求,并返回transaction信息
5.app将购买的内容展示给用户
内置模式可以这样进行本地验单 //本地验证 - (void)localPaymentTransactionVerify:(NSString *)environment body:(NSData *)postData transaction:(SKPaymentTransaction *)transaction{ NSURL *StoreURL = nil; if ([environment isEqualToString:@"environment=Sandbox"]) { StoreURL = [[NSURL alloc] initWithString: ITMS_SANDBOX_VERIFY_RECEIPT_URL]; }else { StoreURL = [[NSURL alloc] initWithString: ITMS_PRODUCT_VERIFY_RECEIPT_URL]; } NSLog(@"运行环境是--- %@", StoreURL); NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:StoreURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; [request setHTTPMethod:@"POST"]; [request setHTTPBody:postData]; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; NSString *product = transaction.payment.productIdentifier; NSLog(@"transaction.payment.productIdentifier++++-----%@",product); if ([product length] > 0) { NSArray *tt = [product componentsSeparatedByString:@"."]; NSString *bookid = [tt lastObject]; if([bookid length] > 0) { NSLog(@"打印bookid------%@",bookid); } } //在此做交易记录 // Remove the transaction from the payment queue. [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } #pragma mark connection delegate - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { //进行二次验证; // NSLog(@" 以下是HTTP协议的监听,若由服务器验证,可不用这段代码%@", [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]); NSLog(@" 以下是HTTP协议的监听,若由服务器验证,可不用这段代码%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); NSDictionary * dic=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; NSNumber *status=dic[@"status"]; NSDictionary * receiptDic=dic[@"receipt"]; // NSString *purchased=receiptDic[@"product_id"]; NSArray * tempArr=receiptDic[@"in_app"]; NSString * purchased=nil; for (int i=0 ; i<tempArr.count; i++) { NSDictionary * tempPurchase=tempArr[i]; purchased=tempPurchase[@"product_id"]; } if (status.intValue==0) { // 发送通知更改账户V豆的数量; // [[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchasedNotification object:purchased]; } else { NSLog(@"收据校验失败"); switch (status.intValue) { case 21000: NSLog(@"App Store不能读取你提供的JSON对象"); break; case 21002: NSLog(@"receipt-data域的数据有问题"); break; case 21003: NSLog(@"receipt无法通过验证"); break; case 21004: NSLog(@"提供的shared secret不匹配你账号中的shared secret"); break; case 21005: NSLog(@"receipt服务器当前不可用"); break; case 21006: NSLog(@"receipt合法,但是订阅已过期。服务器接收到这个状态码时,receipt数据仍然会解码并一起发送"); break; case 21007: NSLog(@"receipt是Sandbox receipt,但却发送至生产系统的验证服务"); break; case 21008: NSLog(@"receipt是生产receipt,但却发送至Sandbox环境的验证服务"); break; default: break; } } NSLog(@"%@",dic[@"status"]); } - (void)connectionDidFinishLoading:(NSURLConnection *)connection{ // NSLog(@" 以下是HTTP协议的监听,若由服务器验证,可不用这段代码"); } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ NSLog(@" 以下是HTTP协议的监听,若由服务器验证,可不用这段代码"); NSLog(@"%@+++",response); switch([(NSHTTPURLResponse *)response statusCode]) { case 200: NSLog(@"200------"); break; case 206: NSLog(@"206------"); break; case 304: NSLog(@"304------"); break; case 400: NSLog(@"400------"); break; case 404: NSLog(@"404------"); break; case 416: NSLog(@"416------"); break; case 403: NSLog(@"403------"); break; case 401: NSLog(@"401------"); case 500: NSLog(@"500------"); break; default: break; } }
2、服务器模式的流程
服务器模式的流程:
*******最重要的一点:在确认服务端收到receipt之前不要结束订单(不要调用[[SKPaymentQueue defaultQueue] finishTransaction:transaction];)******
1.app从服务器获取产品标识列表
2.app从app store 获取产品信息
3.用户选择需要购买的产品
4.app 发送 支付请求到app store
5.app store 处理支付请求,返回transaction信息
6.app 将transaction receipt 发送到服务器
7.服务器收到收据后发送到app stroe验证收据的有效性
8.app store 返回收据的验证结果
9.根据app store 返回的结果决定用户是否购买成功
服务器验证这样处理---在下面这个交易结束方法里进行服务器验证;就不需要上面本地内置模式的代码块了 - (void)completeTransaction:(SKPaymentTransaction *)transaction{ NSLog(@"交易结束"); NSString *productID = transaction.payment.productIdentifier; //验证购买结果 if (productID.length > 0) { //向自己的服务器验证购买凭证 //最好将返回的数据转换成 base64再传给后台,后台再转换回来;以防返回字符串中有特字符传给后台显示空 NSString *result=[[NSString alloc]initWithData:transaction.transactionReceipt encoding:NSUTF8StringEncoding]; NSLog(@"---transactionReceipt:%@", result); NSString *environment = [self environmentForReceipt:result]; // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 从沙盒中获取到购买凭据 NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; NSString *sendString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr]; NSLog(@"_____%@",sendString); //******这个二进制数据由服务器进行验证***************************** NSData *postData = [NSData dataWithBytes:[sendString UTF8String] length:[sendString length]]; //在这里进行服务器验证 //******本地验证*********************************************** [self localPaymentTransactionVerify:environment body:postData transaction:transaction]; //结束交易(收到服务器的验证之后再调用此方法---避免造成漏单) // [[SKPaymentQueue defaultQueue] finishTransaction: transaction]; } }
上述两种模式的不同之处主要在于:交易的收据验证,内建模式没有专门去验证交易收据,而服务器模式会使用独立的服务器去验证交易收据。内建模式简单快捷,但容易被破解。服务器模式流程相对复杂,但相对安全。
二.再说下关于漏单的处理
*****最重要的一点:在确认服务端收到receipt之前不要结束订单(不要调用[[SKPaymentQueue defaultQueue] finishTransaction:transaction];)
漏单:正常玩家购买了却没有收到物品、且自己的服务端没有任何记录iOS的订单。iOS的补单是非常麻烦的,用户提供支付的截图中的订单号我们又不能在itunes 或者其他地方找到相应的订单号。 服务端需要处理一个receipt中携带了多个未处理的订单,即在in-app中有多个支付记录。 因为虽然按正常逻辑,一次只会处理一笔支付,在漏掉以前充值订单的情况下,一个receipt,可能含有多个购买记录,这些记录可能就是没有下发给用户的,需要对receipt 的 in-app记录逐条检查,根据订单记录查看某一单是否已经下发过了。
如果 in_app 里面值为空.看下这个:https://forums.developer.apple.com/thread/8954
参考内容来源:https://www.jianshu.com/p/e7722bc578c0