In App Purchase總結
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];
- [[ECPurchase shared] setProductDelegate:self];
- [[ECPurchase shared] setTransactionDelegate:self];
- [[ECPurchase shared] setVerifyRecepitMode:ECVerifyRecepitModeiPhone];
- [[ECPurchase shared] requestProductData:identifiers];
- [[ECPurchase shared] requestProductData:identifiers];
- [[ECPurchase shared] addPayment:proIdentifier];
- -(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
編寫下面的程式碼: ProductsViewController.h- //
- // ProductsViewController.h
- // IAPDemo
- //
- // Created by luoyl on 12-3-24.
- // Copyright (c) 2012年 http://luoyl.info. All rights reserved.
- //
- #import <UIKit/UIKit.h>
- #import "ECPurchase.h"
- @interface ProductsViewController : UITableViewController
- {
- NSArray *products_;
- }
- @end
- //
- // ProductsViewController.m
- // IAPDemo
- //
- // Created by luoyl on 12-3-24.
- // Copyright (c) 2012年 http://luoyl.info. All rights reserved.
- //
- #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];
- //iap產品編號集合,這裡你需要替換為你自己的iap列表
- NSArray *productIds = [NSArray arrayWithObjects:@"info.luoyl.iap.item1",
- @"info.luoyl.iap.item2",
- @"info.luoyl.iap.item3", nil];
- //從AppStore上獲取產品資訊
- [[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);
- }
- //接收從app store抓取回來的產品,顯示在表格上
- -(void) receivedProducts:(NSNotification*)notification
- {
- if (products_) {
- [products_ release];
- products_ = nil;
- }
- products_ = [[NSArray alloc]initWithArray:[notification object]];
- [self.tableView reloadData];
- }
- // 註冊IapHander的監聽器,並不是所有監聽器都需要註冊,
- // 這裡可以根據業務需求和收據認證模式有選擇的註冊需要
- - (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
- // IAPDemo
- //
- // Created by luoyl on 12-3-24.
- // Copyright (c) 2012年 http://luoyl.info. All rights reserved.
- //
- #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
- // IAPDemo
- //
- // Created by luoyl on 12-3-24.
- // Copyright (c) 2012年 http://luoyl.info. All rights reserved.
- //
- #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
- //從App Store請求產品列表後返回的產品列表
- - (void)didReceivedProducts:(NSArray *)products
- {
- [[NSNotificationCenter defaultCenter] postNotificationName:IAPDidReceivedProducts object:products];
- }
- #pragma mark - ECPurchaseTransactionDelegate
- // 如果不需要收據認證, 則實現以下3個代理函式
- // 即 ECVerifyRecepitModeNone 模式下
- -(void)didFailedTransaction:(NSString *)proIdentifier
- {
- // NSLog(@"交易取消");
- [[NSNotificationCenter defaultCenter] postNotificationName:IAPDidFailedTransaction object:proIdentifier];
- }
- -(void)didRestoreTransaction:(NSString *)proIdentifier
- {
- //NSLog(@" 交易恢復 ");
- [[NSNotificationCenter defaultCenter] postNotificationName:IAPDidRestoreTransaction object:proIdentifier];
- }
- -(void)didCompleteTransaction:(NSString *)proIdentifier
- {
- // NSLog(@" 交易成功 ");
- [self afterProductBuyed:proIdentifier];
- [[NSNotificationCenter defaultCenter] postNotificationName:IAPDidCompleteTransaction object:proIdentifier];
- }
- // 否則, 需要收據認證, 實現以下2個代理函式
- // ECVerifyRecepitModeiPhone 和 ECVerifyRecepitModeServer模式下
- -(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
- {
- //寫下你的邏輯, 加金幣 or 解鎖 or ...
- }
- @end