iOS 內購IAP(In-App Purchases)代碼實現(上)
iOS 內購,也叫內支付,是在iOS應用內部,向蘋果服務器發起購買請求的過程。我們在這邊來講一講代碼的實現過程。還有,在做內購的時候,常常會有丟單現象發生,每到這時候,我的內心幾乎是崩潰的。所以後面我們來講講如何有效地防止丟單。
先簡述一下內購過程。比如說遊戲裏面,你要買一個裝備,這時候你點擊了購買按鈕;遊戲向蘋果服務器發送購買請求,彈出loading框;手機彈出蘋果賬戶和密碼輸入框;輸入完賬戶密碼後,錢打到蘋果那邊,然後提示你購買成功或者失敗;遊戲loading框消失,成功給你發放商品,失敗提示你購買失敗。整個購買過程結束。
當然,遊戲方必須事先在蘋果iTunes後臺,配置好相應的商品和價格。具體的配置過程,網絡上有很多教程,這邊我們就不再贅述。
配置好以後,我們會獲得一個產品ID。這個東西,我們待會代碼中會用到。
準備
先要將StoreKit.framework加入工程中。
然後工程中創建一個類,我們把它稱爲SDKAppStore。 import StoreKit頭文件。
#import <StoreKit/StoreKit.h>
然後在頭部寫上StoreKit的協議
@interface SDKAppStore : NSObject<SKProductsRequestDelegate,SKPaymentTransactionObserver>
SKProductsRequestDelegate是商品請求回調,用來告訴你有沒有這個商品。
SKPaymentTransactionObserver是交易觀察者,用來告訴你交易進行到哪個步驟了。
接下來使用單例的形式
#pragma mark - 獲取單例
+ (instancetype)sharedInstance {
static SDKAppStore* instance = nil;
static dispatch_once_t onceToken = 0;
dispatch_once(&onceToken, ^{
instance = [[SDKAppStore alloc] init];
});
return instance;
}
請求商品信息
交易開始之前,我們先要用productId去請求一下商品的信息。
#pragma mark - 開始支付
- (void)startPay:(SDKPayInfo*)payInfo productId:(NSString*)productId {
self.payInfo = payInfo;
self.productId = productId;
if ([SKPaymentQueue canMakePayments]) {
[self requestProducts:productId];
} else {
//不允許程序內付費購買;
[self.appStoreDelegate sdkAppStoreNotAllowPay];
}
}
#pragma mark - 請求商品信息
- (void)requestProducts:(NSString*)productId {
NSSet *productIds = [NSSet setWithObject:productId];
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIds];
productsRequest.delegate = self;
[productsRequest start];
}
當程序調用startPay這個方法,整個購買交易就開始了。payInfo是記錄了一些用戶信息,到時候要傳到遊戲服務器去的。productId就是上面說的商品ID。
首先用[SKPaymentQueue canMakePayments]判斷一下程序是否可以交易。
然後將productId放到SKProductsRequest裏面,調用一下start,這時候商品ID就被髮送到蘋果服務器了。
在此之前,我們要先設置好回調,不然接受不到返回來的商品信息
productsRequest.delegate = self;
然後重寫回調方法
pragma mark - appstore回調 請求商品信息回調
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *products = response.products;
SKProduct *product = [products count] > 0 ? [products objectAtIndex:0] : nil;
if (product) {
//添加付款請求到隊列
[[SKPaymentQueue defaultQueue] addPayment:payment];
} else {
//無法獲取商品信息
[self.appStoreDelegate sdkAppStoreNoProductInfo];
SDKLog(@"無法獲取商品信息");
}
}
這邊我們先把返回來的商品信息包裝一下,然後放到交易隊列裏面去
[[SKPaymentQueue defaultQueue] addPayment:payment];
這時候就開始交易啦!
交易回調
商品信息放到交易隊列裏面以後,會回調paymentQueue: updatedTransactions:這個方法這個方法。這個方法的作用是告訴你交易進行到哪個步驟了。
#pragma mark - appstore回調 付款請求回調
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction {
for(SKPaymentTransaction *tran in transaction){
switch (tran.transactionState) {
case SKPaymentTransactionStatePurchasing:
//購買中
[self.appStoreDelegate sdkAppStorePaying];
break;
case SKPaymentTransactionStateDeferred:
//購買中 交易被推遲
[self.appStoreDelegate sdkAppStorePaying];
break;
case SKPaymentTransactionStateFailed:
//購買監聽 交易失敗
[self failedTransaction:tran];
break;
case SKPaymentTransactionStatePurchased:
//購買監聽 交易完成
[self completeTransaction:tran];
break;
case SKPaymentTransactionStateRestored:
//購買監聽 恢復成功
[self restoreTransaction:tran];
break;
default:
break;
}
}
}
這邊是一個switch,分別對應調用交易的各個步驟:
- SKPaymentTransactionStatePurchasing 購買中。這時候你可以彈出一個loading框,提示用戶交易正在進行。
- SKPaymentTransactionStateDeferred 交易被推遲。loading框還在。
- SKPaymentTransactionStateFailed 交易失敗。我們調用交易失敗的事務,比如通知用戶交易失敗。取消交易也在交易失敗的範疇。
- SKPaymentTransactionStatePurchased 交易成功。我們調用交易成功的事務,比如到服務器驗證票據和訂單信息。
- SKPaymentTransactionStateRestored 恢復成功。這一個項目在遊戲中通常是沒有用到的。我們可以忽略。
#pragma mark - 交易事務處理
// 交易成功
- (void)completeTransaction:(SKPaymentTransaction *)transaction {
[self checkReceipt:transaction];
[self finishTransaction:transaction wasSuccessful:YES];
}
// 交易失敗
- (void)failedTransaction:(SKPaymentTransaction *)transaction {
[self.appStoreDelegate sdkAppStorePayComplete:NO];
[self finishTransaction:transaction wasSuccessful:NO];
}
// 交易恢復
- (void)restoreTransaction:(SKPaymentTransaction *)transaction {
[self.appStoreDelegate sdkAppStorePayComplete:YES];
[self finishTransaction:transaction wasSuccessful:YES];
}
//結束交易事務
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
所有的事務,到最後都要調用一下finishTransaction這個方法來結束事務。這個方法裏面,我們把剛剛記錄下來的訂單刪除。並且調用一下SKPaymentQueue的finishTransaction。這樣一個完整的交易纔算結束。
如果沒有調用SKPaymentQueue的finishTransaction,交易不會關閉。
在這邊我們把交易的大概流程走了一遍。下一篇我們來講講如何驗證票據和防止丟單。