• iOS内购-收据验证以及漏单情况的处理


    Apple官方收据验证编程指南

    一.先说下验证方式

    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
  • 相关阅读:
    CoCreateInstace 返回未知注册类别错误
    WINCE USB驱动组入
    CreateEvent ResetEvent SetEvent
    AppWidget的范例
    ubuntu下解决无声音的方法
    计算几何与图形学有关的几种常用算法
    Android实现GPS的打开与关闭
    深入剖析Android动画(Animation) (闪烁、左右摇摆、上下晃动等效果)
    中兴手机Linux下开发的方法
    移动网络环境下ReadBuffer的使用
  • 原文地址:https://www.cnblogs.com/GJ-ios/p/13914030.html
Copyright © 2020-2023  润新知