转自:http://iphone.tgbus.com/dev/iosdev/201205/20120523111744.shtml
In App Purchase属于iPhone SDK3.0的新特性,用于在应用程序中购买付费道具,增加新功能,订阅杂志。是应用程序除了植入广告外的另一种取得收益的方式。 虽然Apple的官方文档已经对In App Purhcase这一特性做了比较详尽的解释,但就某些细节方面还是需要编程人员进行尝试和推敲,今天我就对之前项目中实现In App Purchase功能做下简单的总结。
一.In App Purchasep注册流程
1.登陆Apple开发者帐号
2.创建一个新的Apple ID或是选用一个已存在的Apple ID,确定Apple ID的In App Purchase功能可使用:
3.创建develop(用于沙盒测试)和distribution(用于发布)的profile,创建时选择刚才创建的Apple ID。
4.登陆itunes connect,创建一个新的App或选用一个已存在的App,App的Bundle ID要使用步骤2中选用的App Id(注:Bundle ID只能在App创建时指定,且App创建后不能不能被修改);
5. 进入App Information页面,点击“Manage In-App Purchases”按钮,进入 In-App Purchases管理页面:
5.点击“Create New”按钮开始创建一个新的Iap产品,目前苹果提供的iap产品有5类:
- Consumable:消耗类,可重复购买,每次使用都需要收费
- Non-Consumable:非消耗类,购买一次,永久使用,重复购买不收费;
- Auto-Renewable Subscriptions:自动更新订阅,购买后有使用期限,App Store会自动在使用期限到了后提示使用者重新购买,我们的程序需要有一套机制验证subscription的有效性来决定使用者是否能继续使用;
- Free Subscription:
- Non-Renewing Subscription:
6. 以Non-Consumable为例,创建一个iap产品:
在iap创建页面有若干配置需要我们填写:
- Reference Name:在使用in-app purchase的时候会显示在iTunes Connect和销售报表里面,而不会显示在程序里;
- Product ID:也叫做“product identifier”,这是一个唯一的字符串,用来标识当前的in-app purchase产品,通常的做法是,使用应用的bundle id,然后在最后加一个唯一的字符串;
- Add Language:用户购买时弹出窗口显示的内容,可以设置多个国家的语言实现本地化显示;
- Cleared for Sale:当应用程序上架的时候,程序内置购买功能购买记录清空。
- Price Tier:设置程序内置购买的价钱。
7. 创建了几个Iap产品:
二.在程序中增加 In-App Purchases功能ECPurchase
通过上面的配置,我们已经完成了In-App Purchases服务端的的注册,在程序中,我们可以使用IOS内置库StoreKit.framework里提供的Api实现In-App Purchases产品的购买功能。但如果你不想根据文档再自己写purchase功能,那么有一个第三方的库ECPurchase会适合你。 ECPurchase库封装了purchase的内在逻辑,并且提供了几种验证方式(用于防止iap破解),调用简单方便。ECPurchase库可在文章后面我提供的例子里获得。ECPurchase提供了下面的接口需要开发者自己完成:
1.在App Delegate中添加Observer
- [[ECPurchase shared] addTransactionObserver];
2.设置ECPurchase的product delegate(产品列表代理),transaction delegate(购买结果代理),验证方式
- [[ECPurchase shared] setProductDelegate:self];
- [[ECPurchase shared] setTransactionDelegate:self];
- [[ECPurchase shared] setVerifyRecepitMode:ECVerifyRecepitModeiPhone];
3.请求商品列表
- [[ECPurchase shared] requestProductData:identifiers];
实现代理函数绘制UI
- [[ECPurchase shared] requestProductData:identifiers];
4.购买商品
- [[ECPurchase shared] addPayment:proIdentifier];
5.确认结果
如果不需要收据认证实现代理函数:
- -(void)didFailedTransaction:(NSString *)proIdentifier;
- -(void)didRestoreTransaction:(NSString *)proIdentifier;
- -(void)didCompleteTransaction:(NSString *)proIdentifier;
否则实现代理函数:
- -(void)didCompleteTransactionAndVerifySucceed:(NSString *)proIdentifier;
- -(void)didCompleteTransactionAndVerifyFailed:(NSString *)proIdentifier withError:(NSString *)error;
三.使用ECPurchase实现IAP的例子
下面我会以一个简单的例子演示程序端如何使用ECPurchase完成IAP功能,该例子实现了以下功能:从App Store上获取In App Purchase产品列表,并显示在一个表格(UITableView)上,点击表格上某一个产品名右边的“buy”按钮能购买对应的产品。这个例子主要包含两个类:ProductsViewController 和IAPHandler。
ProductsViewController为UI类,用于显示Iap产品列表和弹出购买结果;
IAPHandler实现ECPurchase与的两个代理接口:ECPurchaseTransactionDelegate和ECPurchaseProductDelegate,ECPurchase的执行结果会被IAPHandler接收处理。 IAPHandler被设计为单例类,目的是防止在iap请求过程中IAPHandler的实例被销毁,导致iap购买结果返回后找不到处理的实例对象。
我们可以在IAPHandler里加入自己的业务逻辑,如购买某个金币产品成功后给玩家帐号增加对应的金币数、购买解锁道具后给游戏解锁等逻辑等。IAPHandler处理了玩家的购买请求后会使用消息中心(NSNotificationCenter)对外发送一条消息,我们可以在相关的视图里接收该消息,更新视图。
首先我们需要创建一个项目,主要项目里设置的Bundle identifier要与itunes connect里指定的一样;
导入以下库:
- StoreKit.framework
- CFNetwork.framework
- SystemConfiguration.framework
- libz.1.2.5.dylib
把ECPurchase库拷贝到项目里(可从下载的例子里获得)
编写下面的代码:
ProductsViewController.h
-
-
-
-
-
-
-
-
- #import <UIKit/UIKit.h>
- #import "ECPurchase.h"
-
- @interface ProductsViewController : UITableViewController
- {
- NSArray *products_;
- }
-
- @end
ProductsViewController.m
-
-
-
-
-
-
-
-
- #import "ProductsViewController.h"
- #import "IAPHandler.h"
-
- @interface ProductsViewController ()
- - (void)registIapObservers;
- @end
-
- @implementation ProductsViewController
-
- - (void)viewDidLoad
- {
- [super viewDidLoad];
- self.title = @"IAP Demo";
- [self registIapObservers];
- [IAPHandler initECPurchaseWithHandler];
-
- NSArray *productIds = [NSArray arrayWithObjects:@"info.luoyl.iap.item1",
- @"info.luoyl.iap.item2",
- @"info.luoyl.iap.item3", nil];
-
- [[ECPurchase shared]requestProductData:productIds];
- }
-
- - (void)viewWillUnload
- {
- [[NSNotificationCenter defaultCenter]removeObserver:self];
- }
-
- - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
- {
- return products_ ? [products_ count] : 0;
- }
-
- - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- {
- static NSString *CellIdentifier = @"Cell";
-
- UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
- if (cell == nil) {
- cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
- cell.selectionStyle = UITableViewCellSelectionStyleNone;
- cell.accessoryType = UITableViewCellAccessoryNone;
- } else {
- for (UIView *view in [cell.contentView subviews]) {
- [view removeFromSuperview];
- }
- }
- SKProduct *product = [products_ objectAtIndex:indexPath.row];
-
- UILabel *localizedTitle = [[UILabel alloc]initWithFrame:CGRectMake(10, 10, 130, 20)];
- localizedTitle.text = product.localizedTitle;
-
- UILabel *localizedPrice = [[UILabel alloc]initWithFrame:CGRectMake(150, 10, 100, 20)];
- localizedPrice.text = product.localizedPrice;
-
- UIButton *buyButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
- buyButton.tag = indexPath.row;
- buyButton.frame = CGRectMake(250, 10, 50, 20);
- [buyButton setTitle:@"Buy" forState:UIControlStateNormal];
- [buyButton addTarget:self action:@selector(buy:) forControlEvents:UIControlEventTouchUpInside];
-
- [cell.contentView addSubview:localizedTitle];
- [cell.contentView addSubview:localizedPrice];
- [cell.contentView addSubview:buyButton];
- return cell;
- }
-
- - (void)getedProds:(NSNotification*)notification
- {
- NSLog(@"通过NSNotificationCenter收到信息:%@,", [notification object]);
- }
-
-
- - (void)buy:(UIButton*)sender
- {
- SKProduct *product = [products_ objectAtIndex:sender.tag];
- NSLog(@"购买商品:%@", product.productIdentifier);
- [[ECPurchase shared]addPaymentWithProduct:product];
- }
-
-
- - (void)viewDidUnload
- {
- [super viewDidUnload];
- }
-
- - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
- {
- return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
- }
-
-
- -(void) receivedProducts:(NSNotification*)notification
- {
- if (products_) {
- [products_ release];
- products_ = nil;
- }
- products_ = [[NSArray alloc]initWithArray:[notification object]];
- [self.tableView reloadData];
- }
-
-
-
- - (void)registIapObservers
- {
- [[NSNotificationCenter defaultCenter]addObserver:self
- selector:@selector(receivedProducts:)
- name:IAPDidReceivedProducts
- object:nil];
-
- [[NSNotificationCenter defaultCenter]addObserver:self
- selector:@selector(failedTransaction:)
- name:IAPDidFailedTransaction
- object:nil];
-
- [[NSNotificationCenter defaultCenter]addObserver:self
- selector:@selector(restoreTransaction:)
- name:IAPDidRestoreTransaction
- object:nil];
-
- [[NSNotificationCenter defaultCenter]addObserver:self
- selector:@selector(completeTransaction:)
- name:IAPDidCompleteTransaction object:nil];
-
- [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(completeTransactionAndVerifySucceed:)
- name:IAPDidCompleteTransactionAndVerifySucceed
- object:nil];
-
- [[NSNotificationCenter defaultCenter]addObserver:self
- selector:@selector(completeTransactionAndVerifyFailed:)
- name:IAPDidCompleteTransactionAndVerifyFailed
- object:nil];
- }
-
- -(void)showAlertWithMsg:(NSString*)message
- {
- UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"IAP反馈"
- message:message
- delegate:nil
- cancelButtonTitle:@"OK"
- otherButtonTitles:nil, nil];
- [alert show];
- [alert release];
- }
-
- -(void) failedTransaction:(NSNotification*)notification
- {
- [self showAlertWithMsg:[NSString stringWithFormat:@"交易取消(%@)",[notification name]]];
- }
-
- -(void) restoreTransaction:(NSNotification*)notification
- {
- [self showAlertWithMsg:[NSString stringWithFormat:@"交易恢复(%@)",[notification name]]];
- }
-
- -(void )completeTransaction:(NSNotification*)notification
- {
- [self showAlertWithMsg:[NSString stringWithFormat:@"交易成功(%@)",[notification name]]];
- }
-
- -(void) completeTransactionAndVerifySucceed:(NSNotification*)notification
- {
- NSString *proIdentifier = [notification object];
- [self showAlertWithMsg:[NSString stringWithFormat:@"交易成功,产品编号:%@",proIdentifier]];
- }
-
- -(void) completeTransactionAndVerifyFailed:(NSNotification*)notification
- {
- NSString *proIdentifier = [notification object];
- [self showAlertWithMsg:[NSString stringWithFormat:@"产品%@交易失败",proIdentifier]];
- }
-
- @end
IAPHandler.h
-
-
-
-
-
-
-
-
- #define IAPDidReceivedProducts @"IAPDidReceivedProducts"
- #define IAPDidFailedTransaction @"IAPDidFailedTransaction"
- #define IAPDidRestoreTransaction @"IAPDidRestoreTransaction"
- #define IAPDidCompleteTransaction @"IAPDidCompleteTransaction"
- #define IAPDidCompleteTransactionAndVerifySucceed @"IAPDidCompleteTransactionAndVerifySucceed"
- #define IAPDidCompleteTransactionAndVerifyFailed @"IAPDidCompleteTransactionAndVerifyFailed"
- #import <Foundation/Foundation.h>
- #import "ECPurchase.h"
-
- @interface IAPHandler : NSObject<ECPurchaseTransactionDelegate, ECPurchaseProductDelegate>
-
- + (void)initECPurchaseWithHandler;
- @end
IAPHandler.m
-
-
-
-
-
-
-
-
- #import "IAPHandler.h"
-
- @interface IAPHandler()
- -(void)afterProductBuyed:(NSString*)proIdentifier;
- @end
- @implementation IAPHandler
-
- static IAPHandler *DefaultHandle_ = nil;
-
- + (void)initECPurchaseWithHandler
- {
- @synchronized(self) {
- if (!DefaultHandle_) {
- DefaultHandle_ = [[IAPHandler alloc]init];
- }
- }
- }
-
- - (id)init
- {
- if (self = [super init]) {
- [[ECPurchase shared] addTransactionObserver];
- [[ECPurchase shared] setProductDelegate:self];
- [[ECPurchase shared] setTransactionDelegate:self];
- [[ECPurchase shared] setVerifyRecepitMode:ECVerifyRecepitModeiPhone];
- }
- return self;
- }
-
- #pragma mark - ECPurchaseProductDelegate
-
- - (void)didReceivedProducts:(NSArray *)products
- {
- [[NSNotificationCenter defaultCenter] postNotificationName:IAPDidReceivedProducts object:products];
- }
-
- #pragma mark - ECPurchaseTransactionDelegate
-
-
- -(void)didFailedTransaction:(NSString *)proIdentifier
- {
-
- [[NSNotificationCenter defaultCenter] postNotificationName:IAPDidFailedTransaction object:proIdentifier];
- }
-
- -(void)didRestoreTransaction:(NSString *)proIdentifier
- {
-
- [[NSNotificationCenter defaultCenter] postNotificationName:IAPDidRestoreTransaction object:proIdentifier];
- }
-
- -(void)didCompleteTransaction:(NSString *)proIdentifier
- {
-
- [self afterProductBuyed:proIdentifier];
- [[NSNotificationCenter defaultCenter] postNotificationName:IAPDidCompleteTransaction object:proIdentifier];
- }
-
-
-
- -(void)didCompleteTransactionAndVerifySucceed:(NSString *)proIdentifier
- {
- [self afterProductBuyed:proIdentifier];
- [[NSNotificationCenter defaultCenter] postNotificationName:IAPDidCompleteTransactionAndVerifySucceed object:proIdentifier];
- }
-
- -(void)didCompleteTransactionAndVerifyFailed:(NSString *)proIdentifier withError:(NSString *)error
- {
- [[NSNotificationCenter defaultCenter] postNotificationName:IAPDidCompleteTransactionAndVerifyFailed object:proIdentifier];
-
- }
-
-
- -(void)afterProductBuyed:(NSString*)proIdentifier
- {
-
- }
- @end
本文例子项目
DEMO