iOS內購-收據驗證以及漏單情況的處理

Apple官方收據驗證編程指南

=================1.先說下驗證方式==============

IOS 內購支付兩種模式:

  1. 內置模式

  2. 服務器模式

內置模式的流程:
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;
    }
}

服務器模式的流程:
*******最重要的一點:在確認服務端收到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];
        
    }
}

上述兩種模式的不同之處主要在于:交易的收據驗證,內建模式沒有專門去驗證交易收據,而服務器模式會使用獨立的服務器去驗證交易收據。內建模式簡單快捷,但容易被破解。服務器模式流程相對復雜,但相對安全。

=================2.再說下關于漏單的處理==============

官方產考文檔:https://developer.apple.com/library/content/technotes/tn2413/_index.html#//apple_ref/doc/uid/DTS40016228-CH1-TNTAG1

*****最重要的一點:在確認服務端收到receipt之前不要結束訂單(不要調用[[SKPaymentQueue defaultQueue] finishTransaction:transaction];)

漏單:正常玩家購買了卻沒有收到物品、且自己的服務端沒有任何記錄iOS的訂單。iOS的補單是非常麻煩的,用戶提供支付的截圖中的訂單號我們又不能在itunes 或者其他地方找到相應的訂單號。

服務端需要處理一個receipt中攜帶了多個未處理的訂單,即在in-app中有多個支付記錄。 
因為雖然按正常邏輯,一次只會處理一筆支付,在漏掉以前充值訂單的情況下,一個receipt,可能含有多個購買記錄,這些記錄可能就是沒有下發給用戶的,需要對receipt 的 in-app記錄逐條檢查,根據訂單記錄查看某一單是否已經下發過了。


如果 in_app 里面值為空.看下這個:https://forums.developer.apple.com/thread/8954

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容