IAP全称叫做 In App Purchase,在应用内购买,顾名思义就是可以在iPhone应用程序里实现二次购买。当然,对于一些免费的应用,IAP是除了植入广告外的另一种取得收益的方式。自从iPhone SDK3.0更新以来,IAP已经得到广泛的应用。我在开发第一个iPhone游戏中就用到了这种功能,这里就简单说一下我实现的具体过程。

这篇文章是针对那些已经对IAP有一定的认识,只是在实现方面遇到困难的人,所以,很多地方我不会进行很详细的解析。

我在游戏中实现道具购买,它是消费型的(Consumable),即可重复购买。

在代码实现之前,你要登陆自己的开发者账号,创建用于开发测试的Apple ID(ID不要带有*,创建成功会默认启动IAP功能,如果没有,点击ID列表最右侧Config,启用In App Purchase功能)。然后生成相应的profile,这些操作就是生成证书和签名,使你的Xcode跟开发者账号联系起来调试,如果不太懂,可以参考一篇比较详细的文章http://www.cocoachina.com/bbs/read.php?tid=7923&page=1 ,里面还包括发布程序的教程。

如果你的Xcode与开发者账号联系起来,那就继续往下看吧。

用你的开发者账号进入iTunes Connect,点击位于左偏上的Manager Your Applications

如果你还没创建你的应用,请点击左上角的Add New App ,这步我会跳过,如果不会创建你的应用,可以参考http://jamesli.cn/blog/?p=1045

创建成功之后,点击进去,再点击右边第二项 Manage In-App Purchases

点击左上角Create New 添加你的道具,选择Consumable ,根据提示填好相关内容,记住,Product ID是以后进行purchase操作的唯一识别,在代码里也会使用到,相当于主键,而且一旦添加后即使删除了以后也不允许再次使用这一ID(官方建议使用域名的命名模式com.companyname.appname.productid),确保你勾选了Clear for Sale。

提交之后,是不是就剩下代码实现的部分了呢?

答案是否定的,你还需要注册一个调试账号,用于IAP调试,即购买时你要用这个账号密码去购买(真机调试前一定要在设置里注销退出自己的真实APP账号),如果用真实的APP账号购买,你的账号余额可能会无缘无故变少了(嘿嘿,我没试过,不知是不是真的会扣你的钱),当然,你有勇气的话,可以尝试一下。

在用你的开发者账号进入iTunes Connect的页面的左下角有Manage Users选项,点击进去,在Test User里新建一个调试账号。

好了,一切都准备好了吧,打开你的Xcode,开始你的代码实现吧!

由于时间有限,我暂时用一个傻瓜方式(直接贴代码)教你如何实现

我一共使用了ToolStoreObserver ToolStoreManager ToolStore三个类,第一个类是默认的,第二个是半默认的,有一部分需要根据自己情况修改,第三个类是自己新建的,主要用来绘制商城的界面以及响应购买按钮事件,已调用第二个类的函数。

 在此之前,先添加一个StoreKit framework包。

在定义全局变量的地方将Product ID赋给一个变量

#define P_Id_2 @"com.companyname.appname.productid"



ToolStoreObserver.h

#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>

@interface ToolStoreObserver : NSObject<SKPaymentTransactionObserver> {

	
}
	
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions;
- (void) failedTransaction: (SKPaymentTransaction *)transaction;
- (void) completeTransaction: (SKPaymentTransaction *)transaction;
- (void) restoreTransaction: (SKPaymentTransaction *)transaction;

@end



ToolStoreObserver.m

#import "ToolStoreObserver.h"
#import "ToolStoreManager.h"

@implementation ToolStoreObserver

- (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) failedTransaction: (SKPaymentTransaction *)transaction
{	
    if (transaction.error.code != SKErrorPaymentCancelled)		
    {		
        // Optionally, display an error here.		
    }	
	[[ToolStoreManager sharedManager] paymentCanceled];
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];	
}

- (void) completeTransaction: (SKPaymentTransaction *)transaction
{		
    [[ToolStoreManager sharedManager] provideContent: transaction.payment.productIdentifier];	
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];	
}

- (void) restoreTransaction: (SKPaymentTransaction *)transaction
{	
    [[ToolStoreManager sharedManager] provideContent: transaction.originalTransaction.payment.productIdentifier];	
    [[SKPaymentQueue defaultQueue] finishTransaction: transaction];	
}

@end



ToolStoreManager.h

#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#import "ToolStoreObserver.h"
#import "cocos2d.h"

@protocol ToolStoreKitDelegate <NSObject>
@optional
- (void)productPurchased;
- (void)failed;
-(void)stop;
-(void)start;
@end

@interface ToolStoreManager : NSObject  <SKProductsRequestDelegate> {

	NSMutableArray *purchasableObjects;
	ToolStoreObserver *storeObserver;	
	double productPrice;
	id<ToolStoreKitDelegate> delegate;
}

@property (nonatomic, retain) id<ToolStoreKitDelegate> delegate;
@property (nonatomic, retain) NSMutableArray *purchasableObjects;
@property (nonatomic, retain) ToolStoreObserver *storeObserver;
@property double productPrice;
- (void) requestProductData;

- (void) buyFeature:(int) Id;

-(void)paymentCanceled;

- (void) failedTransaction: (SKPaymentTransaction *)transaction;
-(void) provideContent: (NSString*) productIdentifier;

+ (ToolStoreManager*)sharedManager;

- (BOOL) featurePurchased;



@end



ToolStoreManager.m

#import "ToolStoreManager.h"

@implementation ToolStoreManager


@synthesize purchasableObjects;
@synthesize storeObserver;
@synthesize delegate;
@synthesize productPrice;
// all your features should be managed one and only by StoreManager
static NSString *featureId_2 =P_Id_2;
static NSString *featureId_5 =P_Id_5;
static NSString *featureId_10 =P_Id_10;

BOOL featurePurchased;

static ToolStoreManager* _sharedStoreManager; // self

-(BOOL) featurePurchased {
	
	return NO;
}

+ (ToolStoreManager*)sharedManager
{
	@synchronized(self) {
        
        if (_sharedStoreManager == nil) {
			
            [[[self alloc] init]autorelease]; // assignment not done here
			_sharedStoreManager.purchasableObjects = [NSMutableArray array];			
			[_sharedStoreManager requestProductData];
			
			_sharedStoreManager.storeObserver = [[[ToolStoreObserver alloc]init]autorelease];
			[[SKPaymentQueue defaultQueue] addTransactionObserver:_sharedStoreManager.storeObserver];
        }
    }
    return _sharedStoreManager;
}

- (void) requestProductData
{
	SKProductsRequest *request=[[[SKProductsRequest alloc] initWithProductIdentifiers: 
								 [NSSet setWithObjects:featureId_2,featureId_5,featureId_10,nil]]autorelease];// add any other product here
	request.delegate = self;
	[request start];
}


- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
	[purchasableObjects addObjectsFromArray:response.products];
	// populate your UI Controls here
	for(int i=0;i<[purchasableObjects count];i++)
	{
		
		SKProduct *product = [purchasableObjects objectAtIndex:i];
		NSLog(@"Feature: %@, Cost: %f, ID: %@",[product localizedTitle],
        [[product price] doubleValue], [product productIdentifier]);
		self.productPrice=[[product price] doubleValue];
        
	}
	
	//[request autorelease];
}
- (void) buyFeature:(int) Id
{
    
    NSString*  featureId;
    
    if([delegate respondsToSelector:@selector(start)])
		[delegate start];
    
    switch (Id) {
        case 2:
            featureId=featureId_2;
            break;
        case 5:
            featureId=featureId_5;
            break;
        case 10:
            featureId=featureId_10;
            break;
            
        default:
            featureId=@"";
            break;
    }
    
    
	if ([SKPaymentQueue canMakePayments])
	{
		SKPayment *payment = [SKPayment paymentWithProductIdentifier:featureId];
		[[SKPaymentQueue defaultQueue] addPayment:payment];
	}
	else
	{
		UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"お客様のAppStore購入は設定されていません。個人設定を再度お確かめ下さい。"
													   delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
		[alert show];
		[alert release];
		if([delegate respondsToSelector:@selector(stop)])
			[delegate stop];
	}
}



-(void)paymentCanceled
{
	if([delegate respondsToSelector:@selector(failed)])
		[delegate failed];
}

- (void) failedTransaction: (SKPaymentTransaction *)transaction
{
	if([delegate respondsToSelector:@selector(failed)])
		[delegate failed];
	
	NSString *messageToBeShown = [NSString stringWithFormat:@"Reason:%@, You can try: %@", [transaction.error localizedFailureReason], [transaction.error localizedRecoverySuggestion]];
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Unable to complete your purchase" message:messageToBeShown
												   delegate:self cancelButtonTitle:@"OK" otherButtonTitles: nil];
	[alert show];
	[alert release];
}

-(void) provideContent: (NSString*) productIdentifier
{
    
    if([_sharedStoreManager.delegate respondsToSelector:@selector(productPurchased)])
        [_sharedStoreManager.delegate productPurchased];
    //[ToolStoreManager updatePurchases:productIdentifier];
}


- (void)requestDidFinish:(SKRequest *)request{
	if (request!=nil) {
		request=nil;
	}
}

#pragma mark Singleton Methods

+ (id)allocWithZone:(NSZone *)zone

{	
    @synchronized(self) {
		
        if (_sharedStoreManager == nil) {
            _sharedStoreManager = [super allocWithZone:zone];			
            return _sharedStoreManager;  // assignment and return on first allocation
        }
    }
	
    return nil; //on subsequent allocation attempts return nil	
}


- (id)copyWithZone:(NSZone *)zone
{
    return self;	
}


- (void)dealloc {
	
	[_sharedStoreManager release];
	[storeObserver release];
	[super dealloc];
}

@end



ToolStore.h

#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#import <netdb.h>
#import "SimpleAudioEngine.h"
#import "cocos2d.h"
#import <Foundation/Foundation.h>
#import "ToolStoreManager.h"
#import <SystemConfiguration/SCNetworkReachability.h>




@interface ToolStoreLayer : CCLayerColor <ToolStoreKitDelegate,UIAlertViewDelegate>
{
    CCLabelTTF *_label;
    CCMenuItem *buy099;//购买0.99刀的道具按钮
   
    int tool_id;
    
}

@property (nonatomic, retain) CCLabelTTF *label;
@end


@interface  ToolStoreScene :CCScene {
    
    ToolStoreLayer *_layer;
}
+(id) scene;
@property(nonatomic, retain) ToolStoreLayer *layer;


@end



ToolStore.m

#import "ToolStore.h"



@implementation ToolStoreScene
@synthesize layer = _layer; 
+(id) scene 
{
    CCScene *scene = [CCScene node]; 
    CCLayer* layer = [ToolStoreScene node];
    [scene addChild:layer]; 
    return scene;
} 

-(id)init{
    if ((self = [super init])) {   
        self.layer = [ToolStoreLayer node];   
        [self addChild:_layer]; 
        
    }
    return self;
}


- (void)dealloc {   
    [ _layer release];   
    _layer = nil;   
    [super dealloc];   
} 

@end
@implementation ToolStoreLayer   

@synthesize label = _label;

-(id) init {   
    
    if( (self=[super initWithColor:ccc4(0,0,0,0)] )) {  
        CGSize winSize = [[CCDirector sharedDirector] winSize];
        
         //添加按钮
        //购买0.99刀道具按钮
        buy099 = [CCMenuItemImage 
                         itemFromNormalImage:@"0.99.png" selectedImage:@"0.99.png" target:self selector:@selector(buy099)];
        buy099.position = ccp(winSize.width/6,winSize.height/6);
        
        CCMenu *starMenu = [CCMenu menuWithItems:buy099, nil];
        starMenu.position = CGPointMake(CGPointZero.x,CGPointZero.y-10) ;
        [self addChild: starMenu z:2];
       
    }   
    return self;  
}  

-(void) buy099{
    
    UIAlertView * alert =[[UIAlertView alloc]
                          initWithTitle:NSLocalizedString(@"Buy two suits of tools?  " , @"Buy two suits of tools? ")  
                          message:nil
                          delegate:self 
                          cancelButtonTitle:NSLocalizedString(@"Buy" , @"Buy") otherButtonTitles:NSLocalizedString(@"Cancel" , @"Cancel"), nil];
    alert.tag=2;
    [alert show];
    [alert release];
    
    
    
} 

- (BOOL)isNetworkReachable{  
    // Create zero addy  
    struct sockaddr_in zeroAddress;  
    bzero(&zeroAddress, sizeof(zeroAddress));  
    zeroAddress.sin_len = sizeof(zeroAddress);  
    zeroAddress.sin_family = AF_INET;  
    
    // Recover reachability flags  
    SCNetworkReachabilityRef defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress);  
    SCNetworkReachabilityFlags flags;  
    
    BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);  
    CFRelease(defaultRouteReachability);  
    
    if (!didRetrieveFlags)  
    {  
        return NO;  
    }  
    
    BOOL isReachable = flags & kSCNetworkFlagsReachable;  
    BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;  
    return (isReachable && !needsConnection) ? YES : NO;  
}  

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    tool_id=alertView.tag;
    if (tool_id>1) {
        switch (buttonIndex) {
            case 0:
                
                if (![self isNetworkReachable]) {
                    
                    UIAlertView* dialog = [[UIAlertView alloc] init];
                    [dialog setDelegate:self];
                    [dialog setMessage:@"Service is unreachable!"];
                    [dialog addButtonWithTitle:@"ok"];
                    [dialog show];   
                    [dialog release];
                    break;
                }
                [ToolStoreManager sharedManager].delegate =self;
                [[ToolStoreManager sharedManager] buyFeature:tool_id];
                break;
            default:
                break;
        }
    }
     
}

#pragma ToolStoreManger Delegate
-(void)productPurchased
{
    NSLog(@"success!");
    
    UIAlertView* dialog = [[UIAlertView alloc] init];
    [dialog setDelegate:self];
    [dialog setMessage:@"success!"];
    [dialog addButtonWithTitle:@"ok"];
    [dialog show];   
    [dialog release];

    //购买成功则保存响应数据
    [GameStartLayer saveToolDate];
}


- (void)dealloc {   
    [_label release];   
    _label = nil;   
    [super dealloc];   
}  



@end



最后,我列出几个要注意的地方

确保你所用来创建Profile的Apple ID启用了In App Purchase功能。

确保你的Apple ID的identifier中没有*。

确保你的bundle ID和你的Apple ID的identifier一致。

确保你的product ID是唯一的。

确保你在代码中所请求的product ID与你在iTunes Connect里添加的一致。

确保你勾选了Clear for Sale。

7.在测试的时候你可能需要等待你的商品添加入Apple的测试沙盒,这个过程可能需要几个小时。

8.在你第一次上传应用程序的时候,确保勾选了需要绑定至该应用程序的商品列表。

9.确保你是在SDK3.0以上编写的。

希望这个教程能给你带来一些收获,期待你出色的iPhone应用 !