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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM