最近自己做了個iOS內建付費的功能,寫下心得。
1
建立APPID,就是唯一標識這個APP的identifer.
登入開發中心,https://developer.apple.com/devcenter/ios/index.action網站好像改版了,跟網上的教學都不一致),依次點選紅框裡的連結,選擇右上角的+號,建立一個唯一的ID,一般是域名的倒寫加上一些其他的東西,反正唯一就行了。這裡取com.baidu.test
2
建立測試用的空APP,(就是沒有實際的程式,只是用來測試內建付費的)
還是登入開發中心,如上1圖,選擇iTunes connect,登入。框1進去後是你所有的APP,框2帳號,測試帳號管理,框3是銀行卡 稅率等資訊,這個務必要寫完整。
3
先說框3,你的銀行資訊必須是完整的,像下圖那樣。否則你在建立產品列表時,產品的種類就可能只有免費訂閱,消耗品,非消耗品等是看不見的。
4
框1 MYAPP。進去後頂上有個+號,就是新建APP,上面有提示,一步一步填寫就行了,有些資訊是不必要的,你可以save一下,看看有沒有報錯什麼的。頂部會有一行連結,如圖,我只填了price和inAPP purchase。inAPP purchase就是設定你的產品列表的地方,點開後如圖,點選create,建立一個假的商品,也可以很多個,產品的ID必須是唯一的,可以用你的APPID+產品名什麼的。這裡假設是com.baidu.test.product1
5
框2,用來建立沙盒測試帳號,選擇下圖紅框,填入一個郵箱地址,隨便編,還有密碼,郵箱不需要驗證的。這裡假設是sahetester@qq.com 這一步做完,準備工作就做好了,然後就是編碼了。
6
先說一下IAP的大致流程,如圖,首先你得先有一個在iTunes設定的產品列表,這樣才知道向iTunes請求什麼商品,這個列表可以寫死,或者放到伺服器上動態獲取,然後拿著這個列表通過apple的API去請求產品,返回後顯示到介面,使用者操作發起一個購買請求,成功或失敗會有相應的回撥方法,成功的時候applestore會產生一個收據,這個收據可以反饋給伺服器用來向store驗證,然後就是伺服器向玩家發放道具什麼的。這裡說的非常粗,詳見程式碼。對於交易恢復restoreTransaction,我不是特別理解這個概念,網上找到一張圖2,有更明白的歡迎指教。
剛搜了一下,有一條:
恢復交易資訊(Transactions)當transaction被處理並從佇列移除之後,正常情況下,程式就再也看不到它們了。 如果你的程式提供的是非消耗性的或是訂閱類的商品,就必須提供restore的功能,使使用者可以在其他裝置上重新儲存購買資訊。
是不是說,如果這個商品是非消耗品,購買完成之後,再次拿著它的ID去請求它就請求不到了??????所以才需要恢復????
7
程式碼的編寫需要匯入StoreKit.framework,需要用到它的SKPayment,SKPaymentQueue,SKPaymentTransaction,SKPaymentTransactionObserver類。8
1.假設我們已經有了一個產品列表:NSSet *productIdentifiers = [NSSet setWithObjects:@"com.baidu.test.product1", nil];2.我們用這個列表去向商店請求商品的具體資訊,這個請求通過類SKProductsRequest完成:建立SKProductsRequest物件,SKProductsRequest * _request=[[[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers] autorelease];建立後還不能立即請求,因為我們要回收結果,所以要實現一些的回撥方法。在網上找了找,說是設定代理delegate,自己強行理解了下,這個delegate和java C#裡的介面差不多,規定一套實現者必須執行的動作,我學objectC才第2天,理解的不好請指教。那麼這個代理該怎麼設定呢:_request.delegate = self;咋一看的暈暈的,把自己設成代理,然後看了下所在類的宣告:黑體部分應該就是所謂的代理了,它裡面有一些方法必須實現,這些方法在合適的時機被回撥。@interface IAPHelper : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver> {?@protected? ? NSSet * _productIdentifiers; ? ?? ? NSArray * _products;? ? NSMutableSet * _purchasedProducts;? ? SKProductsRequest * _request;}然後是傳送請求:[_request start];整個方法是這樣的:- (void)requestProducts {?? ?? ? self.request = [[[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers] autorelease];? ? _request.delegate = self;//設定回撥代理物件? ? [_request start];//請求?? ?}回撥方法:這裡只實現了成功時的回撥,如果請求不成功的,可以實現request:didFailWithError://請求成功的回撥- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {?? ?? ? NSLog(@"Received products results...");? ?? ? self.products = response.products;//接收列表? ? self.request = nil;? //Null,釋放記憶體? ? //傳送訊息給介面,介面要接收訊息,必須得先監聽才可以。? ? [[NSNotificationCenter defaultCenter] postNotificationName:kProductsLoadedNotification object:_products]; ? ?}指教。那麼這個代理該怎麼設定呢:_request.delegate = self;咋一看的暈暈的,把自己設成代理,然後看了下所在類的宣告:黑體部分應該就是所謂的代理了,它裡面有一些方法必須實現,這些方法在合適的時機被回撥。@interface IAPHelper : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver> {?@protected? ? NSSet * _productIdentifiers; ? ?? ? NSArray * _products;? ? NSMutableSet * _purchasedProducts;? ? SKProductsRequest * _request;}然後是傳送請求:[_request start];整個方法是這樣的:- (void)requestProducts {?? ?? ? self.request = [[[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers] autorelease];? ? _request.delegate = self;//設定回撥代理物件? ? [_request start];//請求?? ?}回撥方法:這裡只實現了成功時的回撥,如果請求不成功的,可以實現request:didFailWithError://請求成功的回撥- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {?? ?? ? NSLog(@"Received products results...");? ?? ? self.products = response.products;//接收列表? ? self.request = nil;? //Null,釋放記憶體? ? //傳送訊息給介面,介面要接收訊息,必須得先監聽才可以。? ? [[NSNotificationCenter defaultCenter] postNotificationName:kProductsLoadedNotification object:_products]; ? ?}9
假設我們成功取到了產品列表,那麼接下列就是要顯示到介面上了。顯示程式碼就略過了。接下來要做的就是使用者操作後發出購買請求:因為產品的資訊都在self.products中,使用者點選後我們取出對應的產品ID,建立一個購買物件,放入佇列中,關鍵程式碼如下://SKPayment物件SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier];?//載入到佇列中? [[SKPaymentQueue defaultQueue] addPayment:payment];剩下的事情由apple來完成,但是交易的狀態還是要獲取的,獲取狀態通過新增監視:監視最好在建立類範例,或者程式載入時就加上。[[SKPaymentQueue defaultQueue] addTransactionObserver:引數];因為IAPHelper: NSObject?<SKProductsRequestDelegate, SKPaymentTransactionObserver>實現了SKPaymentTransactionObserver代理協定(就是實現了交易狀態改變時的回撥方法),所以『引數』應該設定為IAPHelper的範例。具體是哪個方法呢?我只找到一個://當發生交易事務時回撥該方法,該方法根據對應狀態呼叫合適的方法- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{? ? for (SKPaymentTransaction *transaction in transactions)? ? {? ? ? ? switch (transaction.transactionState)? ? ? ? {? ? ? ? ? ? case SKPaymentTransactionStatePurchased://成功完成事物? ? ? ? ? ? ? ? [self completeTransaction:transaction];? ? ? ? ? ? ? ? break;? ? ? ? ? ? case SKPaymentTransactionStateFailed://事物失敗? ? ? ? ? ? ? ? [self failedTransaction:transaction];? ? ? ? ? ? ? ? break;? ? ? ? ? ? case SKPaymentTransactionStateRestored://? ? ? ? ? ? ? ? [self restoreTransaction:transaction];? ? ? ? ? ? ? ? NSLog(@"已經購買過該商品");?? ? ? ? ? ? ? ?? ? ? ? ? ? default:? ? ? ? ? ? ? ? break;? ? ? ? }? ? }}到這裡基本上就算完了。ct?<SKProductsRequestDelegate, SKPaymentTransactionObserver>實現了SKPaymentTransactionObserver代理協定(就是實現了交易狀態改變時的回撥方法),所以『引數』應該設定為IAPHelper的範例。具體是哪個方法呢?我只找到一個://當發生交易事務時回撥該方法,該方法根據對應狀態呼叫合適的方法- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{? ? for (SKPaymentTransaction *transaction in transactions)? ? {? ? ? ? switch (transaction.transactionState)? ? ? ? {? ? ? ? ? ? case SKPaymentTransactionStatePurchased://成功完成事物? ? ? ? ? ? ? ? [self completeTransaction:transaction];? ? ? ? ? ? ? ? break;? ? ? ? ? ? case SKPaymentTransactionStateFailed://事物失敗? ? ? ? ? ? ? ? [self failedTransaction:transaction];? ? ? ? ? ? ? ? break;? ? ? ? ? ? case SKPaymentTransactionStateRestored://? ? ? ? ? ? ? ? [self restoreTransaction:transaction];? ? ? ? ? ? ? ? NSLog(@"已經購買過該商品");?? ? ? ? ? ? ? ?? ? ? ? ? ? default:? ? ? ? ? ? ? ? break;? ? ? ? }? ? }}到這裡基本上就算完了。10
還有一個收據問題,只有交易狀態是成功(SKPaymentTransactionStatePurchased或者恢復(SKPaymentTransactionStateRestored)時,才會產生收據Receipt,他儲存本次交易的詳細內容,是transaction物件的一個屬性。怎麼獲取呢?網上找了半天,_iOS_6_1之前的版本,儲存在transaction.transactionReceipt.bytes,好像是2進位制,得轉碼什麼的,新版本通過NSBundle的一個方法appStoreReceiptURL來獲取。拿到2進位制的收據經過base64編碼之後就是向Appstore驗證交易是不是真的生效了。沙盒的驗證地址是"https://sandbox.itunes.apple.com/verifyReceipt";正式的驗證地址是https://buy.itunes.apple.com/verifyReceipt,程式碼大致像這樣:NSString* receipt64 = [self encode64:(uint8_t *)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];? ? NSLog(@"receipt64== %@",receipt64);?? ?? ? /*本地驗證? ? NSString *URL=@"https://sandbox.itunes.apple.com/verifyReceipt";? ? //https://buy.itunes.apple.com/verifyReceipt? ? NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];// autorelease];? ? [request setURL:[NSURL URLWithString:URL]];? ? [request setHTTPMethod:@"POST"];? ? //設定contentType? ? [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];? ? //設定Content-Length? ? [request setValue:[NSString stringWithFormat:@"%d", [receipt64 length]] forHTTPHeaderField:@"Content-Length"];?? ?? ? NSDictionary* body = [NSDictionary dictionaryWithObjectsAndKeys:receipt64, @"receipt-data", nil];?? ?? ? SBJsonWriter* w = [SBJsonWriter new];? ? [request setHTTPBody:[[w stringWithObject:body] dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]];?? ?? ? NSHTTPURLResponse *urlResponse=nil;? ? NSError *errorr=nil;? ? NSData *receivedData = [NSURLConnection sendSynchronousRequest:request?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? returningResponse:&urlResponse?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? error:&errorr];?? ?? ? //解析? ? NSString *results=[[NSString alloc]initWithBytes:[receivedData bytes] length:[receivedData length] encoding:NSUTF8StringEncoding];? ? NSLog(@"-Himi-? %@",results);? ? NSDictionary*dic = [results JSONValue];?? ?? ? if([[dic objectForKey:@"status"] intValue]==0){//注意,status=@"0" 是驗證收據成功?? ? ? NSLog(@"valid ok");? ? }大致像這樣:NSString* receipt64 = [self encode64:(uint8_t *)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];? ? NSLog(@"receipt64== %@",receipt64);?? ?? ? /*本地驗證? ? NSString *URL=@"https://sandbox.itunes.apple.com/verifyReceipt";? ? //https://buy.itunes.apple.com/verifyReceipt? ? NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];// autorelease];? ? [request setURL:[NSURL URLWithString:URL]];? ? [request setHTTPMethod:@"POST"];? ? //設定contentType? ? [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"];? ? //設定Content-Length? ? [request setValue:[NSString stringWithFormat:@"%d", [receipt64 length]] forHTTPHeaderField:@"Content-Length"];?? ?? ? NSDictionary* body = [NSDictionary dictionaryWithObjectsAndKeys:receipt64, @"receipt-data", nil];?? ?? ? SBJsonWriter* w = [SBJsonWriter new];? ? [request setHTTPBody:[[w stringWithObject:body] dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES]];?? ?? ? NSHTTPURLResponse *urlResponse=nil;? ? NSError *errorr=nil;? ? NSData *receivedData = [NSURLConnection sendSynchronousRequest:request?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? returningResponse:&urlResponse?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? error:&errorr];?? ?? ? //解析? ? NSString *results=[[NSString alloc]initWithBytes:[receivedData bytes] length:[receivedData length] encoding:NSUTF8StringEncoding];? ? NSLog(@"-Himi-? %@",results);? ? NSDictionary*dic = [results JSONValue];?? ?? ? if([[dic objectForKey:@"status"] intValue]==0){//注意,status=@"0" 是驗證收據成功?? ? ? NSLog(@"valid ok");? ? }11
網上還說發起購買時最好先判斷一些內建購買能不能用,通過if([SKPaymentQueue?canMakePayments]) ?{ ?????...//Display?a?store?to?the?user??} ?else?{ ?????...//Warn?the?user?that?purchases?are?disabled.??}?12
還有就是我對於恢復交易的理解:restoreTransaction,如果你有一個商品是一次性的,玩家已經購買過,因為某種原因玩家的裝置丟失了這個商品,這時候可以通過apple提供的API幫助玩家找回丟失的商品。具體流程是:通過[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];發起恢復,商品成功恢復會回撥?paymentQueueRestoreCompletedTransactionsFinished方法,失敗會回撥paymentQueue:restoreCompletedTransactionsFailedWithError:方法。