IAP-應用內購買流程

?成為ios開發者最大的好處就是,你編寫的應用程序會有很多方式可以賺錢。比如,收費版,免費掛廣告版,還有就是程序內置購買。

? ? 程序內置購買會讓你愛不釋手,主要有以下原因:

  • 除了程序本身的下載收費以外,你還可以賺更多的錢。一些用戶愿意為那些額外的功能花費大量的金錢。
  • 你可以免費發布你的程序(這樣的話,用戶就可以任意下載了),如果他們喜歡這個程序的話,那么就會有人愿意購買額外功能。
  • 在你做完一個程序的時候,你可以在以后的發布版中添加更多的功能,然后這些功能可以用內置購買,這樣的話,你就不用再重新制作另一個程序了。

? ? 我最近正在制作的一個程序里面,我就決定先把程序免費(其中只包含一個故事),然后把更多的故事放在in-app purchase里面。

? ? 在這篇教程里面,你將會學到如何使用程序內置付費來解瑣本地程序里面的內容,我將向你展示一些技巧,用來應付使用程序內置購買功能時的一些異步特性。請謹慎采納這些建議,因為我的程序也還在開發之中,但是,隨著我的知識的積累,我會逐步更新教程內容以確保不誤人子弟。

? ? 這篇教程的前提條件你需要熟悉基本的ios編程概念,如果你還是一個ios開發新手,可以先參考這些教程。

In App Rage

? ?那么,本教程將制作一個怎樣的程序呢?好吧,在揭曉答案之前,我先介紹一些背景情況。。。

? ?最近,我對?rage comics這玩意兒非常著迷。如果你以前從沒聽說過它,讓我向你們介紹一下吧。它們實際上就是一些非常有趣的漫畫,里面有些人非常搞笑和搞怪的人和事。

? ?因此,這篇教程,我們想要做一個非常小巧的應用,叫做“In App Rage”,在這個程序里面,用戶可以使用內置購買來獲得一些漫畫。但是,在我們開始編碼之前,我們需要先用ios Developer Center和iTunes Connect來為本程序創建一個入口點。

? ? 第一步,就是為這個程序創建一個App ID。所以,首先登錄?iOS Developer Center,選擇“App IDs”標簽而,然后點擊“New App ID”,如下圖所示:

? ? 你可以按照下面的截圖,根據提示 輸入描述和bundle identifier:

? ?注意,你不能直接使用上面這個bundle identifier,你需要定義你自己的獨一無二的identifier,通常的做法是把你的域名反過來寫就行了,然后你也可以基于其它規則來制作啦。

? ? 當你完成的時候,點擊Submit。好,恭喜你,你現在有一個新的App ID了!現在,你將使用這個ID在iTunes Connect里面來創建一個新的應用了。

? ?首先登錄?iTunes Connect,點擊“Manage Your Applications”,然后選擇“Add New App”,并輸入依次App Name,SKU number,同時選擇你之前剛剛創建好的Bundle ID。

? ? 你可能不得不在你的應用程序名字上面下點功夫,因為,app名字必須是唯一的,而且我們之前為它添加了一個入口點(entry)。

? ? 接下來的兩頁將要求你輸入你的應用程序的一些信息。現在,可以隨便填一些內容,因為后面還有機會再更改。但是,每個帶×號的文本框你都必須要填好(包括程序截圖,甚至你現在還沒有截圖,呵呵,造一個吧)

? ?好吧,讓你們看看我對于這個過程的感覺吧,請看下圖:

? ? 如果你像上面一樣出錯了,只需要隨便填寫一些數據就可以了。你可以使用任何圖標或者截屏,只要大小合適就行了。一旦你把所有的錯誤都解決完以后,你就大功告成啦,oh yeah!

管理 In App Purchases

? ? 在你開始編寫in app purchase代碼之前,你需要為此創建一個樁應用(placeholder app),同時,你必須在iTunes Connet里面設置好。所以,現在你擁有一個樁應用了,你現在只需要點擊“Manage In App Purchases”按鈕就行了,如下圖所示:

? ? 然后,點擊左上角的“Create New”,然后按照下圖所示,填寫相應的信息:

? ? 讓我們來解釋下這幾個文本域的含義吧:

  • Reference Name:?這個名字就是在使用in-app purchase的時候會顯示在iTunes Connect里面。這個名字你可以隨便取,因為在你的程序里面是看不到它滴。
  • Product ID:?在蘋果的開發文檔里面,這個也叫做“product identifier”,這是一個唯一的字符串,用來標識你的in-app purchase。通常的做法是,使用你的bundle id,然后在最后加一個唯一的字符串。
  • Type:?你可以選擇non-consumable(購買一次,永久使用),comsumable(購買一次,使用一次),或者subscription(自動續款)。本教程中,我們采用non-consumables。
  • Cleared for Sale:?手續已經齊全,可以出售。如果該復選框未選中,in app purchase將不管用。
  • Price Tier:?設置程序內置購買的價錢。

??? 在你完成上面的設置以后,往下滾動鼠標,然后在Display Detail section部分添加一個English entry,如下圖所示:

??? 當你的程序的內置購買功能弄好之后,你查詢App Store的時候會返回你剛剛設置的信息。

??? 你可能會奇怪,為什么我們要設置剛剛這一步(畢竟,你還是可以直接硬編碼在你的程序之中啊!)好吧,很明顯Apple想知道你定的價錢嘛。同時,在App Store里面會根據你填寫的這些東西來顯示一些信息,比如,內置付費應用排行榜。最后,如果你這一步設置了,你之后會變得很輕松。因為,它讓你不用硬編碼這些信息在你的代碼之中。而且可以讓你動態改變是允許內置購買還是禁止內置購買。

??? 一旦你完成之后,保存entry,然后創建更多的實體(entry),和下面的截圖效果類似。不要擔心描述信息,我們并不會在本教程中使用它們。

??? 你可能會注意到,這個過程要花費您不少時間,我能夠相像,當你的程序有很多內置購買功能的時候,這個創建過程會有多么的煩人!幸運的是,本教程我們體會不到,但是,如果你的教程真的遇到了這種情況,呵呵,可以留言給我抱怨一下吧!:)

Retrieving Product List(提取產品列表)

??? 在你能讓用戶從你的程序里面購買任何東西之前,你必須向iTunes Connect發送一個查詢請求來從服務器上提取所有可用的產品列表。

??? 我們可以直接在view controller里面添加代碼來實現之,但是那樣擴展性太不好了,不利于重用。所以,我們將創建一個輔助類來管理所有與in-app purchase相關的內容,然后你就可以在你的其它程序里面重用了。

??? 在從服務器上獲得產品列表的同時,這個輔助類還會記錄哪些產品被購買了,哪些還沒有。它會為每一個已經購買過的產品創建一個identifier,然后把它存到NSUserDefaults里面去。

??? 好了,讓我們動手實驗一下吧!打開XCode,然后選擇File\New Project,再選擇?iOS\Application\Navigation-based Application,點擊Choose。把工程命名為InAppRage,然后點擊Save。

??? 接下來,創建一個新的類來管理內置付費代碼,命名為IAPHelper。首先,點擊Classes分組,選擇File\New File,然后是iOS\Cocoa Touch Class\Objective-C class,確保Subclass of NSObject被選中,然后點擊Next。把這個文件命名為IAPHelper.m,通過確保“Also create IAPHelper.h” 被選中,然后點擊Finish。

??? 我們首先往IAPHelper.m里面添加一個方法來從iTunes Connect里面提取產品列表,代碼如下:

復制代碼
- (void)requestProducts {self.request = [[[SKProductsRequest alloc] initWithProductIdentifiers:_productIdentifiers] autorelease];_request.delegate = self;[_request start];}
復制代碼

??? 這個方法假設我們已經定義了一個實例變量,叫做?_productIdentifiers ,它包含了一串產品標識符,之后用來從iTunes Connect里面查詢產品滴。(比如com.raywenderlich.inapprage.drummerrage)

??? 它然后創建了一個SKProductsRequest實例,那是蘋果公司寫的一個類,它里面包含從iTunes Connect里面提取信息的代碼。使用此類灰常easy,你只需要給它一個delegate(它必須符合SKProductsRequestDelegate 協議),然后就可以調用start方法了。

??? 我們設置IAPHelper類本身作為delegate,那就意味著此類會收到一個回調函數,此函數(productsRequest:didReceiveResponse)會返回產品列表。

Update:?Jerry 在論壇里面指出,SKProductsRequestDelegate 協議是從SKRequestDelegate派生而來滴,而SKRequestDelegate協議有一個方法,叫做?request:didFailWithError:。此方法會在失敗的時候調用,如果你喜歡的話,你可以使用此方法來代碼后面的timeout方法。感謝Jerry!

??? 好吧,接下來讓我們來實現productsRequest:didReceiveResponse 方法吧,具體如下所示:

復制代碼
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {NSLog(@"Received products results...");   self.products = response.products;self.request = nil;    [[NSNotificationCenter defaultCenter] postNotificationName:kProductsLoadedNotification object:_products];    
}
復制代碼

??? 這個非常簡單。它首先保存返回的產品列表(是一個SKProducts的數組),然后把request設置為nil(為了釋放內存),然后發出一個通知,任何偵聽這個通知的對象都會收到這個消息。

??? 接下來添加初始化代碼:

復制代碼
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers {if ((self = [super init])) {// Store product identifiers
        _productIdentifiers = [productIdentifiers retain];// Check for previously purchased products
        NSMutableSet * purchasedProducts = [NSMutableSet set];for (NSString * productIdentifier in _productIdentifiers) {BOOL productPurchased = [[NSUserDefaults standardUserDefaults] boolForKey:productIdentifier];if (productPurchased) {[purchasedProducts addObject:productIdentifier];NSLog(@"Previously purchased: %@", productIdentifier);}NSLog(@"Not purchased: %@", productIdentifier);}self.purchasedProducts = purchasedProducts;}return self;
}
復制代碼

??? 這個初始化代碼將檢測哪些產品已經被購買,哪些還沒有。通過查詢NSUserDefaults可以知道,然后再建立一個適當的數據結構。

??? 好了,現在,我們已經見過最重要的代碼了。接下來,我們在頭文件中添加一些聲明。首先,打開?IAPHelper.h,并作如下修改:

復制代碼
#import <Foundation/Foundation.h>
#import "StoreKit/StoreKit.h"#define kProductsLoadedNotification         @"ProductsLoaded"@interface IAPHelper : NSObject <SKProductsRequestDelegate> {NSSet * _productIdentifiers;    NSArray * _products;NSMutableSet * _purchasedProducts;SKProductsRequest * _request;
}@property (retain) NSSet *productIdentifiers;
@property (retain) NSArray * products;
@property (retain) NSMutableSet *purchasedProducts;
@property (retain) SKProductsRequest *request;- (void)requestProducts;
- (id)initWithProductIdentifiers:(NSSet *)productIdentifiers;@end
復制代碼

?? 這個簡單地導入StoreKit 頭文件,然后定義一些實例變量、函數和通知的名字。

???接下來,在IAPHelper.m里面添加synthesize 代碼,以后內存釋放代碼,如下所示:

復制代碼
// Under @implementation
@synthesize productIdentifiers = _productIdentifiers;
@synthesize products = _products;
@synthesize purchasedProducts = _purchasedProducts;
@synthesize request = _request;// In dealloc
- (void)dealloc
{[_productIdentifiers release];_productIdentifiers = nil;[_products release];_products = nil;[_purchasedProducts release];_purchasedProducts = nil;[_request release];_request = nil;[super dealloc];
}
復制代碼

??? 最后一步,你需要添加StoreKit框架。右鍵點擊Frameworks文件夾,然后點Add\Existing Frameworks ,然后選擇?StoreKit.framework。然后選擇Build\Build 編譯一下,編譯完之后,你的代碼應該是沒有錯誤的。(此方法在Xcode4.0以上不適用。4.0需要點擊工程文件名,然后右鍵target,然后在build phase里面添加框架)

Subclassing for Your App

??? 這里將創建一個IAPHelper類,這樣以后你在你的程序里面只需要繼承一下它,然后指定你的產品標識符(product identifier)就可以啦。許多人給我提建議,說可以從WEB服務器上把產品標識符以及其它相關信息全部弄下來,然后,當你的應用程序需要更新的時候,你就可以動態添加新的in-app purchase了。

??? 這個提議非常好,但是,為了保持本教程的簡單性,我這里就采用了硬編碼的方式。

??? 右鍵選中Classes 分組,然后選擇File\New File,再選擇?iOS\Cocoa Touch Class\Objective-C class,確保Subclass of NSObject 被復選中,然后點擊Next。把這個文件命名為InAppRageIAPHelper.M,同時確保?“Also create InAppRageIAPHelper.h” 被復選中,然后點擊Finish。

?? 然后,把InAppRageIAPHelper.h 替換成下列代碼:

復制代碼
#import <Foundation/Foundation.h>
#import "IAPHelper.h"@interface InAppRageIAPHelper : IAPHelper {}+ (InAppRageIAPHelper *) sharedHelper;@end
復制代碼

??? 這里把InAppRageIAPHelper類定義為IAPHelper類的子類,然后創建了一個靜態方法用來創建些幫助類的單例。

??? 接下來,把InAppRageIAPHelper.m替換成下面的代碼:

復制代碼
#import "InAppRageIAPHelper.h"@implementation InAppRageIAPHelperstatic InAppRageIAPHelper * _sharedHelper;+ (InAppRageIAPHelper *) sharedHelper {if (_sharedHelper != nil) {return _sharedHelper;}_sharedHelper = [[InAppRageIAPHelper alloc] init];return _sharedHelper;}- (id)init {NSSet *productIdentifiers = [NSSet setWithObjects:@"com.raywenderlich.inapprage.drummerrage",@"com.raywenderlich.inapprage.itunesconnectrage", @"com.raywenderlich.inapprage.nightlyrage",@"com.raywenderlich.inapprage.studylikeaboss",@"com.raywenderlich.inapprage.updogsadness",nil];if ((self = [super initWithProductIdentifiers:productIdentifiers])) {                }return self;}@end
復制代碼

??? 第一個sharedHelper方法是為了使InAppRageIAPHelper類變成一個單例類。注意,這種實現單例的方式并不是線程安全的,但是,對于本應用來說完全足夠了,因為我們只有一個主線程。

??? 接下來,我們硬編碼了一組產品標識符的字符串數組,然后調用了基類的初始化方式。注意,我們在這里的字符串名字必須保持和之前在iTunes Connect里面定義的名稱要一致。

??? 然后選擇Build\Build,保證沒有錯誤再繼續哦。

添加幫助類代碼

??? 我們差不多完成了我們的幫助類了,但是,在調用這個類的時候會有兩個問題,我們接下來會討論解決辦法。

?? 第一個問題就是,這段代碼在沒有網絡連接的情況下是跑不起來滴。所以,我們在使用之前,需要檢查是否有網絡。

???第二個問題,加載產品列表可以會耗費一定的時間,所以,我們需要讓用戶知道我們在加載產品列表,而不是神馬都不顯示,那樣用戶會以為程序出問題了。我們只需要簡單的顯示一個activity indicator就可以啦。

?? 關于這兩個問題,我們都可以自己動手來解決,但是,你為什么要重新發明輪子呢?(譯者:工作中,遇到任何“問題”的時候,這里的“問題”,我指的是有點難度的問題,或者自己一時想不清楚的問題,不要急著動手編碼,你還沒想清楚呢!瞎編碼什么呀!不妨google一下,你會有意想不到的收獲。當然,這里我并不是鼓勵大家不動腦筋,而是,有時候,我們程序員需要一種“懶”。)蘋果已經為我們寫好了一個檢測網絡是否可用的代碼,叫做?Reachability class,而?Matej Bukovinski則為我們寫了一個非常好用的指示器類?reusable progress indicator。你完全可以重用他們,而不要去重新發明輪子。

?? 所以,盡管去下載這些源代碼吧,當然,你也可以直接從本教程的源碼中獲得上面提到的源碼。

???一旦你下載完了這些文件,直接把MBProgressHUD.h/m?和 Reachability.h/m拖到你的項目的Classes分組下面就可以啦。同時確保?“Copy items into destination group’s folder”被復選中,然后點擊Add。

?? 最后一步----你需要添加SystemConfiguration 類庫,因為Reachability這個類依賴此類庫。右鍵點擊Frameworks文件夾,然后選擇Add\Existing Frameworks,然后再從列表中選擇SystemConfiguration.framework就可以啦。然后,編譯,確保沒有錯誤后再繼續。

?? 好了,現在我們得到所有的產品列表和價格了,現在讓我們把它們整合起來。

顯示產品列表

??? 打開RootViewController.h ,然后做如下修改:

復制代碼
// Before @interface
#import "MBProgressHUD.h"// Inside @interface
MBProgressHUD *_hud;// After @interface
@property (retain) MBProgressHUD *hud;
復制代碼

?? 上面只是簡單的聲明一些實例變量和定義MBProgressHUD 屬性。

?? 然后,打開RootViewController.m,并做如下修改:

復制代碼
// At top of file
#import "InAppRageIAPHelper.h"
#import "Reachability.h"// Under @implementation
@synthesize hud = _hud;// Uncomment viewDidLoad and add the following
self.title = @"In App Rage";// Uncomment viewWillAppear and add the following
self.tableView.hidden = TRUE;[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(productsLoaded:) name:kProductsLoadedNotification object:nil];Reachability *reach = [Reachability reachabilityForInternetConnection];    
NetworkStatus netStatus = [reach currentReachabilityStatus];    
if (netStatus == NotReachable) {        NSLog(@"No internet connection!");        
} else {        if ([InAppRageIAPHelper sharedHelper].products == nil) {[[InAppRageIAPHelper sharedHelper] requestProducts];self.hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];_hud.labelText = @"Loading comics...";[self performSelector:@selector(timeout:) withObject:nil afterDelay:30.0];}        
}
復制代碼

?? 這里比較重要的代碼在viewWillAppear里面。它首先設置table view默認情況下隱藏(table view在產品列表加載完之后會再重新顯示滴)。然后,設置了一個通告,因為此類需要知道什么時候產品列表加載完了。

?? 然后再使用Reachability 來檢測網絡是否可用。如果可用的話,它就調用IAPHelper的requestProducts 方法來下載之前填好的產品列表。

?? 當產品列表在加載過程中的時候,我們用MBProgressHUD 顯示一個“loading”界面。同時,我們還設置一個超時檢測函數,當30秒過后,如果還沒有加載完產品列表的話,我們就提示用戶錯誤。

?? 所以,接下來,讓我們添加一些代碼來處理通告消息,和超時處理函數。

復制代碼
- (void)dismissHUD:(id)arg {[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];self.hud = nil;}- (void)productsLoaded:(NSNotification *)notification {[NSObject cancelPreviousPerformRequestsWithTarget:self];[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];self.tableView.hidden = FALSE;    [self.tableView reloadData];}- (void)timeout:(id)arg {_hud.labelText = @"Timeout!";_hud.detailsLabelText = @"Please try again later.";_hud.customView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"37x-Checkmark.jpg"]] autorelease];_hud.mode = MBProgressHUDModeCustomView;[self performSelector:@selector(dismissHUD:) withObject:nil afterDelay:3.0];}
復制代碼

?? 第一個函數(dismissHUD)只是一個輔助函數,用來隱藏加載面板的。

?? 第二個方法(productsLoaded)是在kProductsLoadedNotification 通告消息到達的時候被觸發的。它隱藏了加載面板,同時重新加載table view里面的東西,用來顯示down下來的產品列表滴。

?? 最后一個方法(timeout),更新HUD并顯示一個超時的消息,然后讓這個HUD過一段時間再消失。

? 最后,我們需要在?RootViewController.m里面再添加一些代碼來完成table view的動作,代碼如下:

復制代碼
// Replare return 0 in numberOfRowsInSection with the following
return [[InAppRageIAPHelper sharedHelper].products count];// In cellForRowAtIndexPath, change cell style to "subtitle":
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];// In cellForRowAtIndexPath, under "Configure the cell"
SKProduct *product = [[InAppRageIAPHelper sharedHelper].products objectAtIndex:indexPath.row];NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setLocale:product.priceLocale];
NSString *formattedString = [numberFormatter stringFromNumber:product.price];cell.textLabel.text = product.localizedTitle;
cell.detailTextLabel.text = formattedString;if ([[InAppRageIAPHelper sharedHelper].purchasedProducts containsObject:product.productIdentifier]) {cell.accessoryType = UITableViewCellAccessoryCheckmark;cell.accessoryView = nil;
} else {        UIButton *buyButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];buyButton.frame = CGRectMake(0, 0, 72, 37);[buyButton setTitle:@"Buy" forState:UIControlStateNormal];buyButton.tag = indexPath.row;[buyButton addTarget:self action:@selector(buyButtonTapped:) forControlEvents:UIControlEventTouchUpInside];cell.accessoryType = UITableViewCellAccessoryNone;cell.accessoryView = buyButton;     
}// In viewDidUnload
self.hud = nil;// In dealloc
[_hud release];
_hud = nil;
復制代碼

?? 在這里,table view只是簡單的顯示IAPHelper單例里面的產品列表---這個列表我們是通過SKProductsRequest來獲取的。

?? products數組里面的對象都是SKProduct的實例。它們包含了你在iTunes Connect里面設置的信息,比如title,description,price,etc.本教程中,table view只是簡單的顯示價格和標題。同時,我們還添加了一個“購買”按鈕,現在這個“購買”還不起作用,因為我們還沒有為它編碼任何代碼。

?? 你現在差不多可以測試一下了,但是,還有最后一件步(而且是非常重要的一步!)。你需要設置bundle identifier。點擊你的InAppRage-Info.plist并修改Bundle identifier來匹配你的ios Developer Center里面的那個,如下圖所示:

?? 好了,差不多了!編譯并運行你的程序(你需要編譯到設備上面,模擬器上是不行的),然后你會看到一個loading indicator,之后,就會顯示一系列產品列表,如下圖所示:

給我錢看看

??? 這是篇超級無敵又臭又長的教程,而且最重要的部分還是沒有講到---如何處理支付,如何賺錢,接下來,馬上為您揭曉!

??? 做支付基本的幾個要領如下:

  • 你創建一個SKPayment對象,然后指定用戶想要購買的產品的標識符。然后把它加到支付隊列(payment queue)里面去。
  • StoreKit將會提醒用戶“are you sure?”, 然后要求用戶輸入用戶名和密碼,然后支付,然后就會返回給你,支付成功還是失敗。你也可以處理這種情況:用戶已經為此付過費了,然后可以重新再下載,同時給出一個恰當的提示就可以了。
  • 你設計一個特殊的對象來處理支付通告回調消息。這個對象需要處理支付內容下載(在我們這個教程沒必要,因為我們是硬編碼的),同時解瑣程序里面的相關內容(我們可以通過使用NSUserDefaults類來處理,然后把值設置到purchasedProducts 里面就行啦)

?? 不要擔心---當你看到代碼的時候,就會發現,其實這個過程是很easy滴。再強調一次,我們為了使IAPHelper盡可能可以重用,我們將在?IAPHelper.h里面做如下修改:

復制代碼
// Add two new notifications
#define kProductPurchasedNotification       @"ProductPurchased"
#define kProductPurchaseFailedNotification  @"ProductPurchaseFailed"// Modify @interface to add the SKPaymentTransactionObserver protocol
@interface IAPHelper : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver> {// After @interface, add new method decl
- (void)buyProductIdentifier:(NSString *)productIdentifier;
復制代碼

然后打開IAPHelper.m 文件并作如下修改:

復制代碼
- (void)recordTransaction:(SKPaymentTransaction *)transaction {    // Optional: Record the transaction on the server side...    
}- (void)provideContent:(NSString *)productIdentifier {NSLog(@"Toggling flag for: %@", productIdentifier);[[NSUserDefaults standardUserDefaults] setBool:TRUE forKey:productIdentifier];[[NSUserDefaults standardUserDefaults] synchronize];[_purchasedProducts addObject:productIdentifier];[[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchasedNotification object:productIdentifier];}- (void)completeTransaction:(SKPaymentTransaction *)transaction {NSLog(@"completeTransaction...");[self recordTransaction: transaction];[self provideContent: transaction.payment.productIdentifier];[[SKPaymentQueue defaultQueue] finishTransaction: transaction];}- (void)restoreTransaction:(SKPaymentTransaction *)transaction {NSLog(@"restoreTransaction...");[self recordTransaction: transaction];[self provideContent: transaction.originalTransaction.payment.productIdentifier];[[SKPaymentQueue defaultQueue] finishTransaction: transaction];}- (void)failedTransaction:(SKPaymentTransaction *)transaction {if (transaction.error.code != SKErrorPaymentCancelled){NSLog(@"Transaction error: %@", transaction.error.localizedDescription);}[[NSNotificationCenter defaultCenter] postNotificationName:kProductPurchaseFailedNotification object:transaction];[[SKPaymentQueue defaultQueue] finishTransaction: transaction];}- (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];default:break;}}
}- (void)buyProductIdentifier:(NSString *)productIdentifier {NSLog(@"Buying %@...", productIdentifier);SKPayment *payment = [SKPayment paymentWithProductIdentifier:productIdentifier];[[SKPaymentQueue defaultQueue] addPayment:payment];}
復制代碼

?? 啊!好多代碼啊,但是,其實都不難,我會一個個向大家解釋清楚。

?? 當table view里面的buy按照被按下去的時候,它將會調用buyProductIdentifier函數。然后會創建一個新的SKPayment 對象,并且把這個對象加載到隊伍中去。我們將把此類當作delegate來接收支持事務的更新消息,所以,當支付完成 的時候或者失敗的時候,paymentQueue:updatedTransactions 這個函數將會被調用。

?? 如果支付成功了(或者取消了),那么provideContent 函數都會被調用。然后,重點來了---它會在NSUserDefaults里面設置一個標記,然后把這個事務加到隊列中去。剩下的代碼就是用來檢測用戶是否獲得了相應的內容了。

?? 假如支付失敗了,也會相應的有一個失敗的通告消息會到達的。

?? 注意,這里recordTransaction 并沒有任何實現。如果你可以的話,你可以去實現此方法,然后給WEB服務器發送一個消息,讓服務器來做一些記錄。個人來講,我覺得實現這個方法沒什么實際的用處。

? 同時,也請注意,這種方法保存支付信息是非常容易被黑的(你需要加密保存),但是,我并不是很關心這個東東,因為,任何想要破解我的程序的人,他們肯定是不愿意付錢的,in-app對他們來說沒什么意義。

? 在我們使用這些代碼之前,我們還需要在App Delegate里面添加一些東西,這樣的話,當產品支付事務完成的時候,IAPHelper類就會得到相應的通千。所以,打開InAppRageAppDelegate.m并作如下修改:

// At top of file
#import "InAppRageIAPHelper.h"// In application:didFinishLaunchingWithOptions
[[SKPaymentQueue defaultQueue] addTransactionObserver:[InAppRageIAPHelper sharedHelper]];

??? 如果沒有這句代碼的話,那么?paymentQueue:updatedTransactions 這個函數將不會被調用,所以,造成記得要加上去!

?? 最后一步,讓我們回到table view上面來。打開RootViewController.m ,然后作如下修改:

復制代碼
// Add new method
- (IBAction)buyButtonTapped:(id)sender {UIButton *buyButton = (UIButton *)sender;    SKProduct *product = [[InAppRageIAPHelper sharedHelper].products objectAtIndex:buyButton.tag];NSLog(@"Buying %@...", product.productIdentifier);[[InAppRageIAPHelper sharedHelper] buyProductIdentifier:product.productIdentifier];self.hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];_hud.labelText = @"Buying fable...";[self performSelector:@selector(timeout:) withObject:nil afterDelay:60*5];}// Add inside viewWillAppear
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(productPurchased:) name:kProductPurchasedNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(productPurchaseFailed:) name:kProductPurchaseFailedNotification object: nil];// Add new methods
- (void)productPurchased:(NSNotification *)notification {[NSObject cancelPreviousPerformRequestsWithTarget:self];[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];    NSString *productIdentifier = (NSString *) notification.object;NSLog(@"Purchased: %@", productIdentifier);[self.tableView reloadData];    }- (void)productPurchaseFailed:(NSNotification *)notification {[NSObject cancelPreviousPerformRequestsWithTarget:self];[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];SKPaymentTransaction * transaction = (SKPaymentTransaction *) notification.object;    if (transaction.error.code != SKErrorPaymentCancelled) {    UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Error!" message:transaction.error.localizedDescription delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil] autorelease];[alert show];}}
復制代碼

?? 你就要成功啦,再堅持一小會兒!

In App Purchases, Accounts, and the Sandbox

?? 當你在XCODE里面運行你的程序的時候,你并不是在運行真正的In-App Purchase服務器---你實際上是跑在沙盒服務器上面。

?? 這意味著,你可以購買任何東西而不用擔心會被扣錢。但是,你需要先創建一個測試帳號,同時確保你的設備登出了apple store,這樣的話,你就可以看到這個處理過程了。

? 要創建測試帳號,你可以先登際?iTunes Connect ,然后點擊“Manage Users”.點擊“Test User”, 然后就可以創建一個測試帳號了。

? 然后,打開你的iphone,確保你退出當前的帳號了。你可以通過打開Settings程序,然后點擊"Store",然后點"Sign out”。(大家千萬注意啊!)

? 最后,運行你的程序吧。然后點擊購買,輸入測試帳號信息,如果一切順利的話,你會得到如下截屏的輸出!

?? 但是,等一分鐘---哪有里漫畫啊!!!!你沒值錢當然就沒有啦。。。

?? 好吧,這篇教程已經足夠長了,用戶購買以后可以得到漫畫的任務就交由讀者來完成吧。

???這里有相應的漫畫圖片資源,你可以拿去用用。

何去何從?

?? 本項目完整源代碼:sample project

?? 如果你想學習更多關于程序內置購買的內容,請參考蘋果的文檔?In-App Purchase Programming Guide。

?? 同時,也請留意Noel Llopis 寫的一些非常不錯的文章。

?

?? 要論壇交流,請點擊傳送門!

?

?


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/444847.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/444847.shtml
英文地址,請注明出處:http://en.pswp.cn/news/444847.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++(8.5)--Vector容器

向量容器Vector1. 定義/初始化2. 遍歷3. 常用操作vector 迭代器遍歷&#xff0c;sort, reverse,1. 定義/初始化 vector是同一類型對象的集合&#xff0c;被稱作容器。vector實際是一個類模版&#xff0c;可用于保存多種數據類型的數據&#xff08;確定類型的vector 就只能裝同…

關于mysql的change和modify

前端時間要寫個游戲里的郵件系統&#xff0c;定義了一個如下的表結構&#xff1a; CREATE TABLE sysmail (mailid int(20) NOT NULL AUTO_INCREMENT,sendtime int(11) NOT NULL DEFAULT 0,mailtitle varchar(512) COLLATE utf8_bin NOT NULL DEFAULT ,mailcontext varchar(2048…

終于,我讀懂了所有Java集合——map篇

首先&#xff0c;紅黑樹細節暫時擼不出來&#xff0c;所以沒寫&#xff0c;承諾年前一定寫 HashMap &#xff08;底層是數組鏈表/紅黑樹&#xff0c;無序鍵值對集合&#xff0c;非線程安全&#xff09; 基于哈希表實現&#xff0c;鏈地址法。 loadFactor默認為0.75&#xff0…

valgrind工具使用詳解

zz自 http://blog.csdn.net/destina/article/details/6198443 感謝作者的分享&#xff01; 一 valgrind是什么&#xff1f; Valgrind是一套Linux下&#xff0c;開放源代碼&#xff08;GPL V2&#xff09;的仿真調試工具的集合。Valgrind由內核&#xff08;core&#xff09;以…

C++(9)--裸指針、智能指針、引用

指針1.裸指針的基本概念1.1 裸指針的聲明*/初始化&1.2 操作裸指針--間接運算符*1.3 裸指針使用 demo--指向一個簡單變量1.4 空指針--nullptr1.5 特殊指針--void *ptr2.指針和引用--引用定義&3.指針和數組3.1 數組指針的定義3.2 數組指針遞增/遞減操作3.3 指針與數組使用…

關于valgrind的安裝和內存泄露分析

程序的安裝 如果使用的是tar包安裝. valgrind # wget http://valgrind.org/downloads/valgrind-3.9.0.tar.bz2 # tar -jxvf valgrind-3.9.0.tar.bz2 # cd valgrind-3.9.0 # ./autogen.sh # ./configure # make; make install 使用命令: valgrind --tool=memcheck --leak-…

關于mysql的cpu占用高的問題

現在游戲開了泰服 ,發現泰服的cpu占用率總是比繁體或者大陸的高很多,每次都是占用了300%多 top - 15:34:06 up 222 days, 2:51, 2 users, load average: 0.75, 0.73, 0.66 Tasks: 215 total, 1 running, 214 sleeping, 0 stopped, 0 zombie Cpu(s): 52.4%us, 8.5%…

網絡原理知識點匯總

OSI七層模型 vs. TCP/IP 五層模型&#xff08;有時候也說四層&#xff09;及各層協議 七層&#xff1a;物理層&#xff1a;為數據端設備提供傳送數據的通路&#xff0c; IEEE802 數據鏈路層&#xff1a;提供介質訪問和鏈路管理&#xff0c; ARP&#xff0c;MTU 網絡層&#xf…

無限踩坑系列(8)--猿界神猿

計算機一句話冷知識1.GNU2. Unix與C語言3. Linux與git-hub4. c/c 編譯器5. python1.GNU GNU是一個自由的操作系統&#xff0c;其內容軟件完全以GPL方式發布。 GNU&#xff1a;GNU’s Not Unix!的遞歸縮寫 Unix 商業化之后&#xff0c; RMS發起了GNU計劃&#xff0c;在該計劃下…

C++實現md5加密或計算文件的唯一性識別

由于網絡上傳了很多關于C實現md5加密的類&#xff0c;至于那個是原創&#xff0c;我不敢妄加猜測&#xff0c;只是這里我聲明我是轉載的&#xff0c;并支持原創。 對于md5加密算法&#xff0c;我提供兩文件&#xff1a; #ifndef MD5_H #define MD5_H #include <string>…

Crontab的格式

第1列分鐘1&#xff5e;59 第2列小時1&#xff5e;23&#xff08;0表示子夜&#xff09; 第3列日1&#xff5e;31 第4列月1&#xff5e;12 第5列星期0&#xff5e;6&#xff08;0表示星期天&#xff09; 第6列要運行的命令 下面是crontab的格式&#xff1a; 分 時 日 月 星期 要…

leetcode516 最長回文子序列

給定一個字符串s&#xff0c;找到其中最長的回文子序列。可以假設s的最大長度為1000。 示例 1: 輸入: "bbbab" 輸出: 4 一個可能的最長回文子序列為 "bbbb"。 示例 2: 輸入: "cbbd" 輸出: 2 一個可能的最長回文子序列為 "bb"。 …

C++(10)--動態分配內存new,程序的內存分配

動態分配內存1. 動態分配內存1.1使用new分配內存1.2使用delete釋放內存1.3使用new創建動態分配的數組2. 程序的內存分配3.數組與指針案例實踐4.二維數組與指針《老九學堂C課程》《C primer》學習筆記。《老九學堂C課程》詳情請到B站搜索《老九零基礎學編程C入門》-------------…

社交app應用開發 客戶端+服務器源碼

原帖地址&#xff1a;http://www.devdiv.com/iOS_iPhone-想學習移動社交APP的童鞋有福了&#xff0c;圖文展示&#xff0c;附客戶端&#xff0c;服務端源碼。-thread-121444-1-1.html 想學習移動社交APP的童鞋有福了&#xff0c;圖文展示&#xff0c;附客戶端&#xff0c;服務…

leetcode83 刪除排序鏈表中的重復元素

給定一個排序鏈表&#xff0c;刪除所有重復的元素&#xff0c;使得每個元素只出現一次。 示例 1: 輸入: 1->1->2 輸出: 1->2 示例 2: 輸入: 1->1->2->3->3 輸出: 1->2->3 思路&#xff1a;判斷下一個是否相同即可。 /*** Definition for singl…

tcpdump的用法

第一種是關于類型的關鍵字&#xff0c;主要包括host&#xff0c;net&#xff0c;port, 例如 host 210.27.48.2&#xff0c;指明 210.27.48.2是一臺主機&#xff0c;net 202.0.0.0 指明 202.0.0.0是一個網絡地址&#xff0c;port 23 指明端口號是23。如果沒有指定類型&#xff0…

關于NFS服務器的原理總結和mount掛載

NFS 是Network File System的縮寫,即網絡文件系統。一種使用于分散式文件系統的協定,由Sun公司開發,于1984年向外公布。功能是通過網絡讓不同的機器、不同的操作系統能夠彼此分享個別的數據,讓應用程序在客戶端通過網絡訪問位于服務器磁盤中的數據,是在類Unix系統間實現磁…

leetcode203 移除鏈表元素

刪除鏈表中等于給定值 val 的所有節點。 示例: 輸入: 1->2->6->3->4->5->6, val 6 輸出: 1->2->3->4->5 思路&#xff1a;就刪唄&#xff0c;注意第一個數可能會被刪 /*** Definition for singly-linked list.* public class ListNode {* …

不需要安裝max或者xcode的object C開發環境

有時候很多人在沒有mac開發機的時候&#xff0c;都想著安裝一個虛擬mac機&#xff0c;或者用codeblock去配置成一個OC開發環境&#xff0c;我之前在學習OC的時候就這么辦過&#xff0c;虛擬機卡的要死&#xff0c;codeblock本來就不是專門用來做OC開發的&#xff0c;還要自己弄…