iOS 內購IAP(In-App Purchases)代碼實現(下)

iOS 內購IAP(In-App Purchases)代碼實現(下)

上次介紹了蘋果內購的交易流程,接下來講講獲取票據信息和防止漏單。

爲什麼要獲取票據信息?票據信息是蘋果返回給我們的購買憑證。我們可以拿這個憑證,到蘋果服務器上去驗證真僞,從而確定是否給用戶發放商品。

一般驗證票據的工作,要放到服務器上面去做,這樣才能確保不會被人破解,造成不必要的損失。

獲取票據信息

當系統響應交易隊列回調的時候,即paymentQueue: updatedTransactions: 被調用,我們可以獲得一個SKPaymentTransaction參數。通過SKPaymentTransaction參數,我們可以獲取蘋果返回來的票據。

下面方法使用SKPaymentTransaction來獲取票據信息:

#pragma mark - 獲取票據信息
- (NSData*)receiptWithTransaction:(SKPaymentTransaction*)transaction {
    NSData *receipt = nil;
    if ([[NSBundle mainBundle] respondsToSelector:@selector(appStoreReceiptURL)]) {
        NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
        receipt = [NSData dataWithContentsOfURL:receiptUrl];
    } else {
        if ([transaction respondsToSelector:@selector(transactionReceipt)]) {
            //Works in iOS3 - iOS8, deprected since iOS7, actual deprecated (returns nil) since iOS9
            receipt = [transaction transactionReceipt];
        }
    }
    return receipt;
}

獲取票據的方式有兩種。一種是直接獲取SKPaymentTransaction裏面的屬性transactionReceipt。這種方式,在iOS7已經廢棄了,到iOS9停用。但是爲了兼容舊機型,我們還是加上這個方式。

一種是使用[[NSBundle mainBundle] appStoreReceiptURL],這種方式是最新的方式,建議使用。

保存訂單信息和票據信息

在拿到票據之後,我們需要將訂單信息,和票據一同傳到遊戲的服務器中。但是在此之前,我們還需要將訂單信息和票據信息在客戶端保存下來。

因爲,如果請求遊戲的服務器失敗,那麼訂單和票據將不能順利抵達遊戲服務器。也就是說用戶花了錢買的的道具,將不被髮放。這樣就造成丟單漏單,到時候每天會一大波用戶到客服去投訴的。

所以,爲了不必要的麻煩,我們在客戶端保存一份訂單信息,等到確定服務器收到以後,我們再將訂單信息,從客戶端刪除。

獲取到的票據,是一個NSData類型。我們現將票據,進行base64編碼。

//base64編碼
NSString *encodingReceipt = [receipt base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];

然後將訂單信息和票據,用url參數的格式串起來。

//獲取url參數
NSString *urlParas = [NSString stringWithFormat:@"order=%@&receipt=%@" ,order ,encodingReceipt];

然後將這一串字符串,保存下來。先將這一串字符串,放到一個NSArray中,然後再用UserDefaults保存下來。

爲什麼要放到一個NSArray中?因爲假如我們有多個訂單沒有發送到服務器,那麼把他們都加到一個數組中,在合適的時機將他們通通拿出來,來一次統一請求,是不是很方便呢?

連接服務器

這邊我們對NSURLConnection進行了簡單地封裝。也可以使用其他網絡框架。

#pragma mark - 連接服務器
- (void)connectServer:(NSString*)urlStr urlParas:(NSString*)urlParas {
    //向服務器發送驗證請求
    FYHttpConnection *conn = [[FYHttpConnection alloc] initWithRequest:urlStr postStr:urlParas];
    [conn executeRequest:^(NSHTTPURLResponse *response, NSDictionary *data) {

        SDKLog(@"---response data---%@", data);

        NSNumber *code = data[@"code"];
        if (code.intValue == 0) {
            //交易驗證成功,做交易成功處理
            [self.appStoreDelegate sdkAppStorePayComplete:YES];
            //從隊列刪除訂單信息
            [self removeUrlParameters:urlParas];
            SDKLog(@"交易驗證成功");
        } else {
            //交易驗證失敗,做交易失敗處理
            [self.appStoreDelegate sdkAppStorePayComplete:NO];
            //從隊列刪除訂單信息
            [self removeUrlParameters:urlParas];
            SDKLog(@"交易驗證失敗");
        }
    } failure:^(NSHTTPURLResponse *response, NSDictionary *data, NSError *error) {
        //再請求
        [self checkUnchekReceipt];
        SDKLog(@"網絡異常");
    }];
}

當請求服務器成功,我們把訂單和票據從客戶端刪除;當請求服務器失敗的時候,我們調用[self checkUnchekReceipt]

驗證遺漏的票據

#pragma mark - 驗證遺漏的票據
- (void)checkUnchekReceipt {
    //取出票據
    NSArray *urlParas = [self loadUrlParameters];
    if ((!urlParas) || (urlParas.count == 0)) {
        return;
    }
    for (NSString *urlPara in urlParas) {
        [self connectServerForUncheckReceipt:urlPara];
    }
}

我們把保存在客戶端,未請求成功的訂單和票據,一個個拿出來,再請求一遍。

- (void)connectServerForUncheckReceipt:(NSString*)urlPara {
    //向服務器發送驗證請求
    FYHttpConnection *conn = [[FYHttpConnection alloc] initWithRequest:SDKAppStoreCheckUrl postStr:urlPara];
    [conn executeRequest:^(NSHTTPURLResponse *response, NSDictionary *data) {

        SDKLog(@"---response data---%@", data);
        NSNumber *code = data[@"code"];
        if (code.intValue == 0) {
            //交易驗證成功,做交易成功處理
            //從隊列刪除訂單信息
            [self removeUrlParameters:urlPara];
            SDKLog(@"交易驗證成功");
        } else {
            //交易驗證失敗,做交易失敗處理
            //從隊列刪除訂單信息
            [self removeUrlParameters:urlPara];
            SDKLog(@"交易驗證失敗");
        }
    } failure:^(NSHTTPURLResponse *response, NSDictionary *data, NSError *error) {
        //再請求
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)delayTime * NSEC_PER_SEC);
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            [self connectServerForUncheckReceipt:urlPara];
        });
        SDKLog(@"遺漏訂單驗證網絡異常");
    }];
}

connectServerForUncheckReceipt方法和connectServer方法沒什麼不同。不過在請求失敗的時候,會開啓一個線程,幾分鐘以後再請求一次,直到請求成功,再把訂單和票據從客戶端刪掉。

爲交易隊列添加觀察者

這樣就萬無一失了嗎?並不是。當你的app在運行的情況下,一直沒有請求成功怎麼辦?沒關係,我們在打開app的時候,也來檢查一遍,有沒有遺漏的訂單。

所以我們在AppDelegateapplication: didFinishLaunchingWithOptions: 方法中添加[[SDKAppStore sharedInstance] checkUnchekReceipt];

有一種情況是,當用戶已經把想要交易的商品,加入到交易隊列裏面了,而paymentQueue: updatedTransactions:卻遲遲得不到響應(有時候蘋果服務器響應真的很慢)。這時候用戶把app關掉了(等得不耐煩了)。所以完蛋了,票據接受不到了。。

不用擔心,蘋果已經爲我們提供瞭解決方案。我們在app剛打開的時候,就把交易隊列的觀察者加上。這樣,如果系統檢查到交易隊列中有未完成的交易,就會去調用代碼中的paymentQueue: updatedTransactions:方法,以保證我們可以收得到票據。

#pragma mark - 添加交易隊列觀察者
- (void)addAppStoreObserver {
    [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}

然後我們在AppDelegateapplication:didFinishLaunchingWithOptions:方法中添加[[SDKAppStore sharedInstance] addAppStoreObserver];

總結

1.先要獲取商品信息。
2.將商品加到交易隊列裏面。
3.根據交易隊列回調,做相應的事務。
4.獲取票據信息。
5.將訂單信息和票據信息保存到客戶端。
6.請求服務器。如果連接成功,將信息從客戶端刪除;連接失敗,繼續請求,直到請求成功。
7.在app剛打開的時候,就添加交易隊列觀察者。

======2016.11.02更新======

關閉交易

在保證你把訂單+票據,傳到自家的服務器之後,記得要關閉交易。

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];

如果沒有關閉交易,系統會自動判定你交易還沒完成。也就是說,在你爲交易隊列添加觀察者的情況下,每次打開app,都有可能會繼續發起未完成的交易。

服務端驗證

在服務端,我們把票據,傳給蘋果服務器去驗證。

發送地址

//測試地址
https://sandbox.itunes.apple.com/verifyReceipt 
//正式地址
https://buy.itunes.apple.com/verifyReceipt 

發送的格式是JSON

{
    "receipt-data":"你的票據"
}

返回的也是一個JSON
這裏寫圖片描述

status表示狀態碼,0表示成功,其他的表示驗證不通過
這裏寫圖片描述

特別說明,21007那個狀態碼,表示你是在測試環境中取的票據,但是卻到正式地址去驗證。

利用這個,我們就不用兩個地址間來回切換了。我們每次先到正式地址去驗證,如果返回21007,我們就再到測試地址驗證一次。

詳情請看:IAP票據驗證

返回的信息裏面有一個receipt字段,也是JSON格式,包含了你票據的信息。

特別說明的是,receipt裏面的in_app字段,這個字段包含了所有你未完成交易的票據信息。也就是在上節說到的關閉交易之後,這個票據信息,就會從in_app中消失。如果不關閉交易,這個票據信息就會在in_app中一直保留。(這個情況可能僅限於你的商品類型爲消耗型)

如果你需要當前票據的唯一號,取in_app中最後一個票據的transaction_id就行。

詳情請看:receipt各字段含義

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章