首页 > 代码库 > iap 详细
iap 详细
附:本文来自IOS6-Tutorias的翻译,本做笔记之用,故语言简练。一,可用的IAP类型:Non-Consumable:用户只需购买一次,不需要再次购买,即可在多台设备上拥有之(restore技术)。Consumable:用户可以购买多次(不限定次数)。例如金币。Auto-Renewable Subscriptions:为了收到app更新的内容,用户需要定期支付款项。(目前仅适用于杂志或者新闻类型的app)。Free Subscriptions:类似于Auto-Renewable Subscriptions类型,但免费,仅适用于杂志类型的app。Non-Renewing Subscriptions:假如你可用Auto-Renewable(你的app不属于杂志或者新闻类型),但是你仍想提供基于时间限制的访问内容,可以选择Non-Renewing来试试。比如你想仅允许用户一周的时间去访问某个特殊的功能,逾期则否。二,在apple上注册IAP:在你的app中提供iap产品之前,你需要让apple知道这个产品,即需要将其注册在iTunesConnect上。流程很简单,稍后你将亲自实践。目前,你需要了解iTunesConnect上的信息:?上图展示了你预填写的product ID,该字串可以认为是一个IAP产品的唯一标示,其价格亦然。上图展示了你的IAP产品的展示信息(本地化名称和描述)。现在你已经完成了IAP的注册。三,在app中实现IAP:总共有7个步骤来实现IAP。1,加载产品identifiers:在开始购买之前,app需要知道你在iTunesConnect中注册的IAP产品的produnct identifiers。(该列表可以硬编码到你的app中,也可以从本地服务器那获取)。NSSet* productIdentifiers = [NSSetsetWithObjects:@"com.razeware.hangman.tenhints",@"com.razeware.hangman.hundredhints",nil]; 2,请求Product 信息:接下来,app将链接AppStore获取产品详细信息。在delegate callback中获取到产品信息,并存贮在SKProduct对象中。_productsRequest= [[SKProductsRequestalloc]initWithProductIdentifiers:productIdentifiers];_productsRequest.delegate= self;[_productsRequeststart]; 3,呈现产品(Present the store):接下来在app中呈现可用的products列表。apple没有提供现成的VC去展示lists,你需要自己创建,因为此,可以做出差异化的购买界面。例如下图:4,触发购买请求:当用户选择一个IAP产品条目时,购买请求API将被触发,之后会自动弹出信息,形如‘你确定购买**产品吗?’SKPayment* payment = [SKPaymentpaymentWithProduct:skProduct];[[SKPaymentQueuedefaultQueue]addPayment:payment]; 5,交易进程:IAP产品API将要求用户支付对应费用,并监听交易成功与否的信息。此时,你可以选择通过向apple servers验证该购买是否有效。(在app新启动时,也将会注册接受交易成功与否的通知。)- (void)paymentQueue:(SKPaymentQueue*)queueupdatedTransactions:(NSArray*)transactions{ for(SKPaymentTransaction* transaction intransactions) { switch(transaction.transactionState){ caseSKPaymentTransactionStatePurchased: [selfcompleteTransaction:transaction]; break; caseSKPaymentTransactionStateFailed: [selffailedTransaction:transaction]; break; caseSKPaymentTransactionStateRestored: [selfrestoreTransaction:transaction]; break; default: break; } }} 6,开放购买内容(unlock the content):这是很关键的步骤,你的app此时应当将购买的内容呈现给用户(即某项功能对用户可用)。7,接受本次交易:最后一步,请求IAP的API,告知本次交易接受。 否则app将认为该交易没有结束,并在下次启动app时,再次提交该交易请求。[[SKPaymentQueuedefaultQueue]finishTransaction: transaction]; ------------------------------------实例:接下来,将开始一个简单的游戏项目,用来展示IAP购买。找到本章资源,解压HangmanCh9Starter项目,打开并运行之。你可能曾经玩过这个游戏。你的目标是猜测屏幕底部的单词,点击label弹出键盘,并键入你认为对的字母。加入你是对的,字母将会显示,反之你的hangman将会被吊起来(没玩过)。假如错误达到一定次数,hangman即死亡。很怪异的游戏,不是吗?试着能不能搞定这个游戏,假如你思维卡住,你可以点击hint按钮来获取一个字母的提示,但是你只有20次的机会。运行下该app的各个地方,你会看到有个setting界面,你可以在里面设置app。在右上角有个‘store’按钮,点击进去,里面是空的,点击restore按钮,将呈现一个新的界面即‘store details’界面。这便是接下来的主要任务。玩成了本任务的项目,用户将会获取更多的单词库,更多的提示来避免hangman死翘翘。(当然对你来讲,你知道最真实的原因:mooooney)。添加IAP到hangmanapp中:1,允许用户购买额外的hint。2,解锁更多的单词库。现有代码初探:略。HMStoreListViewController.m和HMStoreDetailViewController.m目前几乎是空的,这是本章节要完善的内容。设计思考:在写IAP相关代码前还有一件事需要讨论。有2件事已经使IAP简单化:在这部分,我们讲讨论下述2点使IAP简单的原因,或许你会发现一些有用的技术可以借鉴。1,主题和单词库已经被设计为基于文件化的。如果你打算购买一个新的theme,只需要建一个StickmanTheme实例,返回相应的图片和声音元素即可。不需要写新的代码。代码越多,bug出现几率越大。你无法在IAP购买时下载代码。所以增加新内容时你不得不发布新的版本。2,每个主题和单词库以其私有字典被保存。假如你清楚的知道主题和单词库被存放在具体的私有字典中,这将使效果实现更容易。比如,你想要更改到某个theme,你只要代入字典的URL,即可获取相应的theme。这也使得增加主题成为可能,写入文件即可。注:此时文件是以文件夹的形式存贮在项目中。作用:当文件被赋值到app的bundle中时,他们将原封不动的赋值文件下的路径和文件。开工:添加IAP产品到项目(theme,words,hints):前提:拥有ios developer Program账号;确定你已同意iTunes Connect中最新的IOS Developer Program Lisence Agreement;确定你已经完成在iTunes Connect中ios Paid Applications Contract。1,登陆IOS Provisioning Portal,点击‘App IDs‘选项,如下图示:点击’New App ID‘,将出现新建app ID的界面:bundle identifier基于你管理的域名(或者以项目名称即可),例如:com.mypro.pro。注意:App ID不能包含通配符,IAP仅支持明确的App IDs。登陆iTunes Connect ,点击’Manage Your Applications‘,->’Add New App‘,并选择IOS App类型,创建一个基于上述App ID 的app。注意:此时你的app name应当和我的不一样,app name是唯一的,并且我已经创建了该app。点击继续,接下来的2页将会要求你填写关于该app的详细信息,现在可以暂时以占位字母替代,因为你这些信息稍后是可以修改的。为了创建的app可用,你不得不填写所有的信息,包括icon和screenshot(图片大小有严格的要求)。完成app创建,图示:点击右上角的’Manage In-App Purchase‘,然后点击’Create New‘,将出现创建IAP产品的界面:选择IAP 的类型,此处的hint购买,不限制用户购买的次数的,故用’Consumable‘类型。现在拟提供2种hint产品,一个提供10hints,一个100hints。10个hints创建:注:对于Product ID,你应用你自己的反转DNS标示法,比如:com.mypro.pro.tenhins在页面底部,显示In-App Purchase Detaile\lauguage部分,点击’Add Lunguage‘,填写信息,并点击保存之。滚到页面底部,再次保存页面。一个10hints的IAP产品注册成功。100hints 的IAP产品创建:类似10hits的,此处翻译略。你已经完成了在iTunesConnect上的工作,结束进程前,你需要确定的是项目的bundle Identifier是正确的。(折回项目中,点击info-plist文件,找到’Bundle Identifier‘,设置与上述对应的AppId值:完成修改之后,为了避免Xcode使用旧的bundle Id,可以操作如下:1,点击Product\Clean in Xcode。2,删除在device或者在simulator中的app。3,重启Xode,simulator和device。4,运行之。最后一项,调用IAP的API,你需要在项目中添加 StoreKit framework。基于上述操作,已经完成在iTunes Connect中注册IAP产品;项目可用IAP。接下来,将是代码的东东了。------------------------获取Products:加载产品product identifiers并请求product 信息。讲所有这部分代码放在一个helper的类中,方便IAP代码的集中化管理。创建新的Group,命名为’IAP‘。在IAP文件夹下创建NSObject子类文件,命名为“IAPHelper”。在IAPHelper.h中:@interfaceIAPHelper : NSObject- (void)requestProductsWithProductIdentifiers:(NSSet*)productIdentifiers;@end 上述书写的方法,将用于向appstore请求IAP产品信息(produce identifires集合)。在IAPHelper.m中:// 1#import"IAPHelper.h" #import// 2@interfaceIAPHelper() @end@implementationIAPHelper {SKProductsRequest* _productsRequest;// 3}- (void)requestProductsWithProductIdentifiers:(NSSet*)productIdentifiers {// 4_productsRequest= [[SKProductsRequestalloc]initWithProductIdentifiers:productIdentifiers];_productsRequest.delegate= self;[_productsRequeststart];}@end 释义:1,导入storekit头文件,用于调用IAP的APIs。2,为了获取products列表,此处实现了改协议。3,创建request实例,用以请求产品列表。a,保存对request的引用。b,判断其是否运行。4,通过IAP ids产品集合,想appstore请求其对应产品的详细信息,并设定委托,来判断请求的成功与否,并获取成功的信息。在IAPHelper.m @end之前,写入委托方法:#pragma mark - SKProductsRequestDelegate- (void)productsRequest:(SKProductsRequest*)requestdidReceiveResponse:(SKProductsResponse*)response { NSLog(@"Loaded list of products...");_productsRequest= nil;if(!response.products || response.products.count ==0){return;}NSArray* skProducts = response.products;for(SKProduct* skProduct inskProducts) {NSLog(@"Found product: %@ %@ %0.2f",skProduct.productIdentifier,skProduct.localizedTitle,skProduct.price.floatValue);}}- (void)request:(SKRequest*)requestdidFailWithError:(NSError*)error {NSLog(@"Failed to load list of products.");_productsRequest= nil;} 2个代理方法成功和失败的回调。成功:打印出返回的products信息;设置requst==nil。失败:设置request==nil。创建HMIAPHelper。这是因为IAPHelper是完全独立于该项目的,之后在其他的项目可以重用之。有关该项目的代码(比如本项目的IAP产品列表),将添加在特定项目文件中(HMIAPHelper)。创建同上IAPHelper的方法。在HMIAPHelper.h中:#import"IAPHelper.h"@interfaceHMIAPHelper : IAPHelper+ (HMIAPHelper*)sharedInstance;- (void)requestProducts; @end 静态方法:返回一个本类的单例。实例方法:请求产品列表。(无参,将把IAP列表硬编码在里面)在HMIAPHelper.m中#import"HMIAPHelper.h"@implementationHMIAPHelper+ (HMIAPHelper*)sharedInstance {staticdispatch_once_tonce;staticHMIAPHelper * sharedInstance;dispatch_once(&once, ^{sharedInstance = [[selfalloc]init];});returnsharedInstance;}- (void)requestProducts {NSSet* productIdentifiers = [NSSetsetWithObjects:@"com.razeware.hangman.tenhints",@"com.razeware.hangman.hundredhints",nil];return[superrequestProductsWithProductIdentifiers:productIdentifiers];}@end 在requestProducts中,产品列表是硬编码其中的。注意将列表内容换成实际创建的product identifiers。切换到HMStoreListViewController中,#import"HMIAPHelper.h" 在viewDidLoad中:[[HMIAPHelpersharedInstance]requestProducts]; 注意:当在iTunesConnect中创建了IAP产品,将会延迟一段时间才能响应返回产品信息。一般是延迟1~30分钟。有时可能会收到’Cannot connect to iTunes Store‘。这意味着网络有问题,或者iTunes sandbox是关闭的。可以检查下面的URl,如果无回应,则表明是关闭的。https://sandbox.itunes.apple.com/verifyReceipt 更多bug检测,请查看:http://www.raywenderlich.com/forums//viewtopic.php?f=2&t=188展示products:目前已请求到产品列表,现在需要将产品展示出来。从appStore返回的列表信息展示给用户,但是除了SKProduct类提供的,你还需要更多的产品信息。为了信息的清晰化,你需要创建一个类,用以包含产品的信息,即IAPProduct(基于NSObject)在IAPProduct.h中@classSKProduct;@interfaceIAPProduct : NSObject- (id)initWithProductIdentifier:(NSString*)productIdentifier;- (BOOL)allowedToPurchase;@property(nonatomic,assign)BOOLavailableForPurchase;@property(nonatomic,strong)NSString* productIdentifier;@property(nonatomic,strong)SKProduct* skProduct;@end 你可能认为获取到的列表都是可用的,但事实应该通过AppStore检测它们是否可用,并且展示出可用的列表。1,你可能将一个产品刚放加到iTunesConnect上,但它还未同步。2,在app中硬编码的某个产品,可能未在iTunesConnect上创建。打开IAPProduct.m:#import"IAPProduct.h"@implementationIAPProduct- (id)initWithProductIdentifier:(NSString*)productIdentifier { if((self= [superinit])) {self.availableForPurchase =NO;self.productIdentifier = productIdentifier;self.skProduct =nil;}return self;}- (BOOL)allowedToPurchase {if(!self.availableForPurchase) return NO;return YES;}@end 注意:关于allowedToPurchase方法,当产品可以购买时,返回YES,但是过会你将折回到这里,在产品是其它状态时更改以阻止用户购买。折回到IAPHelper和HMIAPHelper,应用IAPProduct新类并返回该类组成的list更改IAPHelper.h如下:typedef void(^RequestProductsCompletionHandler)(BOOLsuccess, NSArray* products);@interfaceIAPHelper : NSObject@property(nonatomic,strong)NSMutableDictionary* products;- (id)initWithProducts:(NSMutableDictionary*)products;- (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler;@end 2处不同点:1,初始化方法的参数变为一个字典:key:product identifier value:一个IAPProduct实例。即该字典含有可用的产品列表。稍后你将不得不打开iTunesConnect。2,requestProducts方法参数是一个block。在IAPHelper.m中,导入IAPProduct头文件:#import"IAPProduct.h" 添加2个新的变量@implementationIAPHelper {SKProductsRequest* _productsRequest;RequestProductsCompletionHandler_completionHandler;}- (id)initWithProducts:(NSMutableDictionary*)products {if((self= [superinit])) {_products= products;}return self;} - (void)requestProductsWithCompletionHandler:(RequestProductsCompletionHandler)completionHandler {// 1_completionHandler= [completionHandlercopy];// 2NSMutableSet* productIdentifiers =[NSMutableSetsetWithCapacity:_products.count];for(IAPProduct* product in_products.allValues) {product.availableForPurchase= NO;[productIdentifiersaddObject:product.productIdentifier]; }// 3_productsRequest= [[SKProductsRequestalloc]initWithProductIdentifiers:productIdentifiers];_productsRequest.delegate= self;[_productsRequeststart];} 1,在将block传给实例变量前,需要copy:This isimportant because if the block that is passed in is on the stack, it won’t beavailable when you need it unless you copy it first as shown here. delegate:- (void)productsRequest:(SKProductsRequest*)requestdidReceiveResponse:(SKProductsResponse*)response {NSLog(@"Loaded list of products...");_productsRequest=nil;// 1NSArray* skProducts = response.products;for(SKProduct* skProductinskProducts) {IAPProduct* product =_products[skProduct.productIdentifier];product.skProduct= skProduct;product.availableForPurchase=YES;}// 2for(NSString* invalidProductIdentifierin response.invalidProductIdentifiers) {IAPProduct* product =_products[invalidProductIdentifier];product.availableForPurchase=NO;NSLog(@"Invalid product identifier, removing: %@",invalidProductIdentifier);}// 3NSMutableArray* availableProducts = [NSMutableArrayarray];for(IAPProduct* productin_products.allValues) {if(product.availableForPurchase) {[availableProductsaddObject:product];}}_completionHandler(YES, availableProducts);_completionHandler=nil;}- (void)request:(SKRequest*)request didFailWithError:(NSError*)error {NSLog(@"Failed to load list of products.");_productsRequest=nil;// 4_completionHandler(FALSE,nil);_completionHandler = nil;} 1,该循环得到的SKProducts,是可用的,并在IAPProducts中找到对应的,并设置其可用。2,除了返回可用列表,也返回不可用列表,可以用invalidProductIdentifiers获取。并设置对应的IAPProduct为不可用。(非必须,但是debug效果显著)3,将可用的列表放进一个数组中,并传给block。4,失败时的block返回。HMIAPHelper的更改打开HMIAPHelper.h,删除requestProducts方法:#import"IAPHelper.h"@interfaceHMIAPHelper : IAPHelper+ (HMIAPHelper*)sharedInstance;@end 传到HMIAPHelper.m中同样删除requestProducts方法:#import"IAPProduct.h" - (id)init {IAPProduct* tenHints = [[IAPProductalloc]initWithProductIdentifier:@"com.razeware.hangman.tenhints"];IAPProduct* hundredHints = [[IAPProductalloc]initWithProductIdentifier:@"com.razeware.hangman.hundredhints"];NSMutableDictionary* products = [@{tenHints.productIdentifier: tenHints,hundredHints.productIdentifier: hundredHints}mutableCopy];if((self= [superinitWithProducts:products])) {}return self;} 打开HMStoreListViewController.m,导入头文件和2个实例:#import"IAPProduct.h"#import @implementationHMStoreListViewController {NSArray* _products;NSNumberFormatter* _priceFormatter;} ——priceFormatter:数字格式化,将有助于讲产品的价格本地化。在viewDidLoad中删除requestProduct,代之以:_priceFormatter= [[NSNumberFormatteralloc]init];[_priceFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];[_priceFormattersetNumberStyle:NSNumberFormatterCurrencyStyle];self.refreshControl= [[UIRefreshControlalloc]init];[self.refreshControladdTarget:selfaction:@selector(reload)forControlEvents:UIControlEventValueChanged];[selfreload];[self.refreshControlbeginRefreshing]; - (void)reload {//1_products=nil;[self.tableViewreloadData];//2[[HMIAPHelpersharedInstance]requestProductsWithCompletionHandler:^(BOOLsuccess,NSArray*products) {if(success) {_products= products;[self.tableViewreloadData];}[self.refreshControlendRefreshing];}];} 1,清空tableView上原有数据。2,请求产品列表,假如请求成功:保存列表,并展示在tableview上。更改tableview的delgate:- (NSInteger)tableView:(UITableView*)tableViewnumberOfRowsInSection:(NSInteger)section{return_products.count;} - (UITableViewCell*)tableView:(UITableView*)tableViewcellForRowAtIndexPath:(NSIndexPath*)indexPath{staticNSString*CellIdentifier =@"Cell";HMStoreListViewCell*cell = [tableViewdequeueReusableCellWithIdentifier:CellIdentifier];IAPProduct*product =_products[indexPath.row];cell.titleLabel.text= product.skProduct.localizedTitle;cell.descriptionLabel.text=product.skProduct.localizedDescription;//1[_priceFormattersetLocale:product.skProduct.priceLocale];cell.priceLabel.text= [_priceFormatter stringFromNumber:product.skProduct.price];returncell;} 运行app:----------------------------------------展示详细视图打开HMStoreDetailViewController.h更改如下:@classIAPProduct;@interfaceHMStoreDetailViewController :UIViewController@property(nonatomic,strong)IAPProduct* product;@end 在HMStoreDetailViewController.m中#import"IAPProduct.h"#import @implementationHMStoreDetailViewController {NSNumberFormatter* _priceFormatter;} - (void)viewDidLoad{[superviewDidLoad];self.view.backgroundColor= [UIColorcolorWithPatternImage:[UIImageimageNamed:@"bg.png"]];_priceFormatter= [[NSNumberFormatteralloc]init];[_priceFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];[_priceFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];} - (void)refresh {self.title=_product.skProduct.localizedTitle;self.titleLabel.text=_product.skProduct.localizedTitle;self.descriptionTextView.text=_product.skProduct.localizedDescription;[_priceFormattersetLocale:_product.skProduct.priceLocale];self.priceLabel.text= [_priceFormatter stringFromNumber:_product.skProduct.price];self.versionLabel.text= @"Version 1.0";if(_product.allowedToPurchase) {self.navigationItem.rightBarButtonItem=[[UIBarButtonItemalloc]initWithTitle:@"Buy"style:UIBarButtonItemStyleBordered target:selfaction:@selector(buyTapped:)];self.navigationItem.rightBarButtonItem.enabled=YES;}else{self.navigationItem.rightBarButtonItem=nil;}self.pauseButton.hidden=YES;self.resumeButton.hidden=YES;self.cancelButton.hidden=YES;}- (void)viewWillAppear:(BOOL)animated {[superviewWillAppear:animated];self.statusLabel.hidden=YES; [selfrefresh];} 转到HMStoreListViewController.m中:#import"HMStoreDetailViewController.h" 加入新方法:#pragma mark - Table view delegate- (void)tableView:(UITableView*)tableViewdidSelectRowAtIndexPath:(NSIndexPath*)indexPath{[selfperformSegueWithIdentifier:@"PushDetail" sender:indexPath];}#pragma mark - Segues- (void)prepareForSegue:(UIStoryboardSegue*)seguesender:(id)sender {if([segue.identifierisEqualToString:@"PushDetail"]) {HMStoreDetailViewController* detailViewController =(HMStoreDetailViewController*)segue.destinationViewController;NSIndexPath* indexPath = (NSIndexPath*)sender;IAPProduct*product = _products[indexPath.row];detailViewController.product= product;}} 因为产品类型是consumable,故清空restoreTapped:方法体。购买产品的实现:现在将开始等待许久的购买模块。该模块将包含4步:1,发送支付请求2,交易进程和结果。3,解锁内容(购买成功)4,结束交易当你购买一个产品时,不允许再次购买同一产品,除非购买中的已完成。(虽然这是不必须的,但是对于用户是好的,以及使app更简洁)。为了达到改目的,打开IAPProduct.h文件,添加变量:@property(nonatomic,assign)BOOLpurchaseInProgress; 在IAPProduct.m中更改如下:- (BOOL)allowedToPurchase {if(!self.availableForPurchase) return NO;if(self.purchaseInProgress) return NO;return YES;} 当iTunesConnect返回产品不可用和该产品正在购买时,用户是不允许购买的。如此甚好。接下来,在IAPHelper中添加一个新方法使用户购买产品:在IAPHelper.h中:@classIAPProduct; - (void)buyProduct:(IAPProduct*)product; 在IAPHelper.m中:- (void)buyProduct:(IAPProduct*)product {//1NSAssert(product.allowedToPurchase,@"This product isn‘tallowed to be purchased!");NSLog(@"Buying %@...", product.productIdentifier);product.purchaseInProgress=YES;SKPayment* payment = [SKPayment paymentWithProduct:product.skProduct];[[SKPaymentQueuedefaultQueue]addPayment:payment];} 1,检测产品是否允许购买。现在需要添加部分代码去鉴定交易是否进行中。在IAPHelper.m中添加交易监听:@interfaceIAPHelper() 方法:- (id)initWithProducts:(NSMutableDictionary*)products {if((self= [superinit])) {_products= products;[[SKPaymentQueuedefaultQueue] addTransactionObserver:self];return self;}} 当IAPHelper初始化时,它便开始监听交易的进行。即apple将实时告诉你有无交易完成。注:关键在于当用户开始一个交易或者完成一个交易的付款,在apple返回成功与否之前,用户突然掉线,终端交易,但用户仍然希望返回购买的东西。所幸,apple已经有解决该问题的办法。即apple将追踪app上没有完成的交易,并通知给监听。但是为了使之工作良好,你需要在app启动时就讲类注册为交易的监听对象。在AppDelegate.m中的解决代码:#import"HMIAPHelper.h" 在application:didFinishLaunchingWithOptiions:的开始:[HMIAPHelpersharedInstance]; 现在在app启动后,它将创建HMIAPHelper单例,并将自己注册为交易监听的对象,并随时被提醒没完成的交易。你需要完成交易监听的协议,在IAPHelper.m中:- (void)paymentQueue:(SKPaymentQueue*)queueupdatedTransactions:(NSArray*)transactions{for(SKPaymentTransaction* transactionintransactions) {switch(transaction.transactionState){caseSKPaymentTransactionStatePurchased:[selfcompleteTransaction:transaction];break;caseSKPaymentTransactionStateFailed:[selffailedTransaction:transaction];break;caseSKPaymentTransactionStateRestored:[selfrestoreTransaction:transaction];default:break;}};} 这是交易监听协议仅要求的一个方法。它提供给你一个正在更新运行的交易列表,唯一要做的就是轮询它们,并根据状态来实施相应的动作。关于complete,和failed状态自不必多讲,关于restored,它是应用于’non-consumable‘类型的IAP产品上的。对于多设备同一app,restored是很重要的,当然也包括同一设备,重装app的情况。3个具体的方法实现:- (void)completeTransaction:(SKPaymentTransaction*)transaction {NSLog(@"completeTransaction...");[sel provideContentForTransaction:transaction productIdentifier:transaction.payment.productIdentifier];}- (void)restoreTransaction:(SKPaymentTransaction*)transaction {NSLog(@"restoreTransaction“);[self provideContentForTransaction:transaction productIdentifier:transaction.originalTransaction.payment.productIdentifier]; }- (void)failedTransaction:(SKPaymentTransaction*)transaction {NSLog(@"failedTransaction...");if(transaction.error.code!=SKErrorPaymentCancelled){NSLog(@"Transaction error: %@",transaction.error.localizedDescription);}IAPProduct* product =_products[transaction.payment.productIdentifier];[selfnotifyStatusForProductIdentifier:transaction.payment.productIdentifierstring:@"Purchase failed."];product.purchaseInProgress=NO;[[SKPaymentQueuedefaultQueue]finishTransaction: transaction]; } complete和restore做了相同的事情,即提供内容。failed调用一个方法,来发出失败的通知,并将产品标记为不再购买中,并结束之。注:结束交易异常重要,否则storeKit无法知道你已结束了该交易,并在app每次启动时都激活该交易。- (void)notifyStatusForProductIdentifier:(NSString*)productIdentifier string:(NSString*)string {IAPProduct* product =_products[productIdentifier];[self notifyStatusForProduct:product string:string];}- (void)notifyStatusForProduct:(IAPProduct*)productstring:(NSString*)string {}- (void)provideContentForTransaction:(SKPaymentTransaction*)transactionproductIdentifier:(NSString*)productIdentifier {IAPProduct* product =_products[productIdentifier];[selfprovideContentForProductIdentifier:productIdentifier];[selfnotifyStatusForProductIdentifier:productIdentifierstring:@"Purchase complete!"];product.purchaseInProgress=NO;[[SKPaymentQueuedefaultQueue]finishTransaction:transaction];}- (void)provideContentForProductIdentifier:(NSString*)productIdentifier { }方法一获取对应的product,并执行另外一个目前为空的notifyStatusForProduct方法(在HMIAPHelper中完善,使该部分功能独立于app)。方法provideContentForTransaction类似于failedTransaction,均是使交易得以结束。但在此之前,它将调用一个目前为空的provideContentForProductIdentifier方法(同样在子类中完成)。接下来,打开HMIAPHelper.m吧。添加代码:#import "HMContentController.h"#import "JSNotifier.h"#import 关于JSNotifier文件:它是Jonah Siegle写的,是在屏幕底部弹出警示框以显示正确或者错误信息。在此用来显示上述提及的notifications。在文件底部添加:- (void)provideContentForProductIdentifier:(NSString *)productIdentifier {if ([productIdentifierisEqualToString:@"com.razeware.hangman.tenhints"]) {int curHints = [HMContentController sharedInstance].hints;[[HMContentController sharedInstance] setHints:curHints + 10];} else if ([productIdentifierisEqualToString:@"com.razeware.hangman.hundredhints"]) {int curHints =[HMContentController sharedInstance].hints;[[HMContentController sharedInstance] setHints:curHints + 100];}}- (void)notifyStatusForProduct:(IAPProduct *)productstring:(NSString *)string {NSString * message = [NSString stringWithFormat:@"%@: %@",product.skProduct.localizedTitle, string];JSNotifier *notify =[[JSNotifier alloc]initWithTitle:message];[notify showFor:2.0];} 方法一是简单的增加购买提示数目(+10 || +100)。方法二弹出警示信息。最后一个步骤是在HMStoreDetailViewController.m中添加购买代码:#import "HMIAPHelper.h" 在空方法buyTapped中添加:- (void)buyTapped:(id)sender {NSLog(@"Buy tapped!");[[HMIAPHelper sharedInstance] buyProduct:self.product];} 现在将开始测试购买:测试购买,你需要一个在App Store sandbox的测试账户,假如目前你还没有,那么登陆iTunes Connect创建吧,选择Manage Users,选择Test User,并Add New User,完成表格并保存之。写下来需要注意的是,如果你在真机上测试,记得将你真实的AppStore账号登出。即Settings/iTunes &App Stores,登出。运行项目,打开product,点击购买,将弹出如下:点击‘buy’,初次购买的话将出现:点击‘Use Existing Apple ID’,填写你刚刚创建的Test User,稍等片刻,你就可以看到购买成功的提示。可以运行程序验证hints数目。--------------------------------------------------------------------购买一个Non-Consumable 类型的IAP产品。现在可以向用户兜售hints,你即将迈步小资生活。加入可以向用户兜售额外的单词库,你将会赚取更多的钱。在之前你很明智的将单词库以文件形式保存,这将有利于一次性添加额外的单词库。现在我们来添加‘Harde words’和‘IOS words’库。实现此,大致需要五步:1,把冰箱门打开。。。。。。。1,测试单词库。2,在iTunes Connect中注册IAP产品。3,修改代码部分。4,恢复(restore)购买5,结束。1,测试单词库在做任何有关IAP之前,第一步便是需要确保你的单词库可以正常工作。(略。即2个plist格式的单词库添加到项目中)。此处单词库是以子文件夹的形式置于项目中的。2,在iTunes Connect中注册IAP产品该步骤非常类似于添加hints产品,但区别在于这边的IAP产品类型要选择Non-consumable。打开iTunes Connect,点击‘Manage Your Applications’,点击项目并进入‘Manage In-App Purchases’,点击‘Create New’,选择Non-consumable\Select,将显示如下:滚动到底部In-App Purchase Details\Language,点击‘Add Language’,填写如下:保存,整体保存。同样步骤完成IOS Words库。3,修改代码。和consumable类型的不同点:1,保持用户是否购买成功的记录,当用户重启app时,解锁对应的产品。2,一个用户仅允许购买一次Non-consumable类型的产品,并非不限次数。3,允许用户在其他的设备上使用已购买的non-consumable类型的产品。基于上述三点,我们来完成相应代码。首先,打开IAPProduct.h,添加新变量来辨别用户是否之前已购买non-consumable产品。@property (nonatomic, assign) BOOL purchase; 在IAPProduct.m中:- (BOOL)allowedToPurchase {if (!self.availableForPurchase) return NO;if (self.purchaseInProgress) return NO;if (self.purchase) return NO;return YES;} IAPHelper不用做任何修改,但需要在HMIAPHelper中做些许修改。首先,添加一个新方法:通过给定的produnctIdentifier和目录解锁单词库:- (void)unlockWordsForProductIdentifier:(NSString *)productIdentifier directory:(NSString *)directory {// 1IAPProduct * product = self.products[productIdentifier];product.purchase = YES;// 2[[NSUserDefaults standardUserDefaults] setBool:YES forKey:productIdentifier];[[NSUserDefaults standardUserDefaults] synchronize];// 3NSURL * resourceURL = [NSBundle mainBundle].resourceURL;[[HMContentController sharedInstance] unlockWordsWithDirURL:[resourceURLURLByAppendingPathComponent:directory]];} 1,通过identifier找到该product,并设置其为‘已购买’。2,记录某产品是否已购买,最简单的方式便是将该信息保存在本地。3,解锁购买内容。现在在provideContentForProductIdentifier的if/else的结尾添加:else if ([productIdentifierisEqualToString:@"com.razeware.hangman.hardwords"]) {[self unlockWordsForProductIdentifier:@"com.razeware.hangman.hardwords" directory:@"HardWords"];} else if ([productIdentifierisEqualToString:@"com.razeware.hangman.ioswords"]) {[self unlockWordsForProductIdentifier:@"com.razeware.hangman.ioswords" directory:@"iOSWords"];} 在购买IAP产品成功时调用该方法,完成对应产品的解锁。另外,在app新启动时,你需要调用解锁方法。即通过NSUserDefault来来判断是否解锁内容。在HMISAPHelper.m中替换:- (id)init {IAPProduct * tenHints = [[IAPProduct alloc] initWithProductIdentifier:@"com.razeware.hangman.tenhints"];IAPProduct * hundredHints = [[IAPProduct alloc]initWithProductIdentifier:@"com.razeware.hangman.hundredhints"];// 1IAPProduct * hardWords = [[IAPProduct alloc]initWithProductIdentifier:@"com.razeware.hangman.hardwords"];IAPProduct * iosWords = [[IAPProduct alloc]initWithProductIdentifier:@"com.razeware.hangman.ioswords"];NSMutableDictionary * products = [@{tenHints.productIdentifier: tenHints,hundredHints.productIdentifier: hundredHints,hardWords.productIdentifier: hardWords,iosWords.productIdentifier: iosWords} mutableCopy];if ((self = [super initWithProducts:products])) {// 2if ([[NSUserDefaults standardUserDefaults] boolForKey:@"com.razeware.hangman.hardwords"]) {[self unlockWordsForProductIdentifier:@"com.razeware.hangman.hardwords"directory:@"HardWords"];}if ([[NSUserDefaults standardUserDefaults]boolForKey:@"com.razeware.hangman.ioswords"]) {[self unlockWordsForProductIdentifier:@"com.razeware.hangman.ioswords"directory:@"iOSWords"];}}return self;} 1,将新增加的non-consumable产品加到Product列表中。如此,在父类IAPHelper中,将会把其添加到SKProductReuest的请求产品列表中。2,在新启动app时,将会依据本地保存的标示,来决定是否解锁对应的内容。打开HMStoreListViewController.m文件,修改以下:在tableView:cellForRowAtindexpath:中,用以下代码替换cell.pricelabel.text:if (product.purchase) {cell.priceLabel.text = @"Installed";} else {cell.priceLabel.text = [_priceFormatter stringFromNumber:product.skProduct.price];} 当用户查看产品列表时,假如已购买的项目,将会显示‘installed’。否则显示购买价格。运行程序,你会看到新建立的产品将会显示在列表中。打开IOS Words,并购买,购买成功显示详细视图:购买成功后查看words列表,你将看到成功添加了新的ios 单词库。但是此时在iap列表详细,你还看不到有任何变化,我们将稍后修改此问题。4,恢复(restore)购买假如你的app中含有non-consumable产品,apple要求你的应用必须允许用户可以恢复购买的功能。首先,打开IAPHelper.h文件,声明一个新方法:- (void)restoreCompletedTransactions; 在IAPHelper.m中:- (void)restoreCompletedTransactions {[[SKPaymentQueue defaultQueue]restoreCompletedTransactions];} 该方法使用storeKit链接至appStore,获取用户已经购买的non-consumable产品,接着调用paymentQueue:updatedTransactions方法中的SKPaymentTransactionStateRestored:(已经完善)。让我们看下GUI部分,打开HMStoreListViewController.m,添加新的类扩展,即实现UIAlertView的委托事件:@interface HMStoreListViewController() @end 在restoreTapped:中:- (void)restoreTapped:(id)sender {NSLog(@"Restore tapped!");UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"Restore Content"message:@"Would you like to check for and restore anyprevious purchases?"delegate:self cancelButtonTitle:@"Cancel"otherButtonTitles:@"OK", nil];alertView.delegate = self;[alertView show];}#pragma mark - UIAlertViewDelegate- (void)alertView:(UIAlertView *)alertViewdidDismissWithButtonIndex:(NSInteger)buttonIndex {if (buttonIndex == alertView.firstOtherButtonIndex) {[[HMIAPHelper sharedInstance] restoreCompletedTransactions];}} 删除app,重装,并点击‘restore’,你的应用应该会执行以上功能。5,结束。在继续之前,你有2个需要完成的点击。1,在你购买一个non-consumable产品后,你会发现仍然是购买按钮显示,并未显示‘installed’字样。点击将会崩溃。因为你有一个断言Assert,去保证这个产品是否允许购买(此处不可购买,因为你已经完成了购买)。你有多种方式来实现更改界面的功能:监听,委托或者其他。但在此,我们将使用KVO。KVO:允许你关注在一个对象中的任何属性的实时变化,此处只需关注purchaseInprogress或者purchase的变化即可,接着刷新。完成该功能,进入HMStoreDetailViewController.m文件,在viewWillAppear中,替换如下:- (void)viewWillAppear:(BOOL)animated {[super viewWillAppear:animated];self.statusLabel.hidden = YES;[self refresh];[self.product addObserver:selfforKeyPath:@"purchaseInProgress" options:0 context:nil];[self.product addObserver:selfforKeyPath:@"purchase" options:0 context:nil];}- (void)viewWillDisappear:(BOOL)animated {[super viewWillDisappear:animated];[self.product removeObserver:selfforKeyPath:@"purchaseInProgress" context:nil];[self.product removeObserver:selfforKeyPath:@"purchase" context:nil];}- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)object change:(NSDictionary *)changecontext:(void *)context {[self refresh];} 当willAppear视图时,将关注2个属性,当disappear的时候,取消关注。当关注的属性有变化时,将调用observeValueForKeyPath,刷新之。2,在产品列表界面需要知道产品状态的变化。打开HMStoreListViewController.m文件,添加变量:@implementation HMStoreListViewController {NSArray * _products; NSNumberFormatter * _priceFormatter;BOOL _observing;} 添加:- (void)viewWillAppear:(BOOL)animated {[super viewWillAppear:animated];[self addObservers];[self.tableView reloadData];}- (void)viewWillDisappear:(BOOL)animated {[super viewWillDisappear:animated];[self removeObservers];}#pragma mark - KVO- (void)addObservers {if (_observing || _products == nil) return;_observing = TRUE;for (IAPProduct * product in _products) {[product addObserver:selfforKeyPath:@"purchaseInProgress" options:0 context:nil];[product addObserver:self forKeyPath:@"purchase" options:0context:nil];}}- (void)removeObservers {if (!_observing) return;_observing = FALSE;for (IAPProduct * product in _products) {[product removeObserver:selfforKeyPath:@"purchaseInProgress" context:nil];[product removeObserver:self forKeyPath:@"purchase"context:nil];}}- (void)observeValueForKeyPath:(NSString *)keyPathofObject:(id)object change:(NSDictionary *)changecontext:(void *)context {IAPProduct * product = (IAPProduct *)object; int row = [_products indexOfObject:product];NSIndexPath * indexPath = [NSIndexPath indexPathForRow:rowinSection:0];[self.tableView reloadRowsAtIndexPaths:@[indexPath]withRowAnimation:UITableViewRowAnimationNone];}- (void)setProducts:(NSArray *)products {[self removeObservers];_products = products;[self addObservers];} 包括添加和删除对product数组元素的监听。修改reload方法,应用setProducts:方法:- (void)reload {[self setProducts:nil];[self.tableView reloadData];[[HMIAPHelper sharedInstance]requestProductsWithCompletionHandler:^(BOOL success, NSArray*products) {if (success) {[self setProducts:products];[self.tableView reloadData];}[self.refreshControl endRefreshing];}];} 运行程序,检测下,完美。验证购买的可用性当你进行IAp购买,对于返回的ok信息你不能100%保证是真从apple那里得来的,加入没有使用‘receipt validation’的话。当你进行IAP购买时,apple将返回给你一块数据,叫做‘receipt’。这是一个带有本次交易的加密签名信息的私有数据块。这么做的主要目当然是为了安全。现在你需要将这个receipt信息发送到apple提供的一个特殊的‘receipt validation’服务器上,用来确保这个是有效的。(不执行这一步的危险性已经被证实:俄罗斯的一个黑客易安装的IAP应用,使用户可以接受任何免费的IAP产品:你配置的DNS使你的设备路由到黑客提供的服务器上,而不是apple的购买服务器上,并且在设备上你还配置了一个欺骗证书,使黑客服务器是可信的。之后不论何时,你进行一个IAP购买请求,请求将转向何可服务器,并且黑客服务器一直返回‘done and paid for’已经购买成功的信息,接着,app将解锁购买的内容--------该黑客在IOS6将不能发挥作用了。然而还有其他的黑客可以实现该欺骗手法)。更多IAP黑客讨论:http://www.macworld.com/article/1167677/hacker_exploits_ios_flaw_for_free_in_app_purchases.html无服务器解决方案:apple官方建议:链接你自己的服务器去验证信息,然后才是建议链接apple的服务器去验证。前者安全性更高。在此暂时介绍无自己服务器的情况即链接apple服务器去验证在解锁内容之前,需要验证。在本项目资源文件中,你将看到有个文件夹:VerificationController,将其拖拽到libs中。并导入security.framework库。在IAPHelper.m中:#import "VerificationController.h" 添加方法:- (void)validateReceiptForTransaction:(SKPaymentTransaction *)transaction {IAPProduct * product =_products[transaction.payment.productIdentifier];VerificationController * verifier =[VerificationController sharedInstance]; [verifier verifyPurchase:transactioncompletionHandler:^(BOOL success) {if (success) {NSLog(@"Successfully verified receipt!");[self provideContentForTransaction:transactionproductIdentifier:transaction.payment.productIdentifier];} else {NSLog(@"Failed to validate receipt.");product.purchaseInProgress = NO;[[SKPaymentQueue defaultQueue]finishTransaction: transaction];}}];} 这个方法是简单的调用apple的代码(略改动)去验证交易的有效性,并据此是否解锁内容。最后是在completetrasaction和restoreTransaction中调用该方法,而不是直接立即提供解锁内容。- (void)completeTransaction:(SKPaymentTransaction *)transaction {NSLog(@"completeTransaction...");[self validateReceiptForTransaction:transaction];}- (void)restoreTransaction:(SKPaymentTransaction *)transaction {NSLog(@"restoreTransaction...");[self validateReceiptForTransaction:transaction];} 运行程序,结果如前,但有了更好的安全性。(引入验证时,注意在verifyPurchase:complete:方法中,用的是sandbox接口,你在发布app前要将其改成buy实际的购买验证接口)。
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。