iOS開發——高級技術&內購服務

 

內購服務數據庫

 

你們都知道作iOS開發自己的收入有三種來源:出售應用、內購和廣告。國內用戶一般不多直接 購買應用,所以對於開發者而言(特別是我的開發者),內購和廣告收入就成了主要的收入來源。內購營銷模式,一般軟件自己是不收費的,可是要得到某些特權就 必須購買一些道具,而內購的過程是由蘋果官方統一來管理的,因此和Game Center同樣,在開發內購程序以前要作一些準備工做(下面的準備工做主要是針對真機的,模擬器省略Provisioning Profile配置過程):數組

 

  1. 前四步和Game Center基本徹底一致,只是在選擇服務時不是選擇Game Center而是要選擇內購服務(In-App Purchase)。服務器

  2. 到iTuens Connect中設置「App 內購買項目」,這裏仍然以上面的「KCTest」項目爲例,假設這個足球競技遊戲中有三種道具,分別爲「強力手套」(加強防護)、「金球」(增長金球率) 和「能量瓶」(提供足夠體力),前二者是非消耗品只用一次性購買,後者是消耗品用完一次必須再次購買。併發

    130913361989836.png

  3. 到iTunes Connect中找到「協議、稅務和銀行業務」增長「iOS Paid Applications」協議,並完成全部配置後等待審覈經過(注意這一步若是不設置在應用程序中沒法得到可購買產品)。app

  4. 在 iOS「設置」中找到」iTunes Store與App Store「,在這裏能夠選擇使用沙盒用戶登陸或者處於註銷狀態,可是必定注意不能使用真實用戶登陸,不然下面的購買測試不會成功,由於到目前爲止咱們的 應用並無真正經過蘋果官方審覈只能用沙盒測試用戶(若是是模擬器不須要此項設置)。框架

  5. 有了上面的設置以後保證應用程序Bundle ID和iTunes Connect中的Bundle ID(或者說App ID中配置的Bundle ID)一致便可準備開發。異步

 

開發內購應用時須要使用StoreKit.framework,下面是這個框架中經常使用的幾個類:ide

 

SKProduct: 可購買的產品(例如上面設置的能量瓶、強力手套等),其productIdentifier屬性對應iTunes Connect中配置的「產品ID「,可是此類不建議直接初始化使用,而是要經過SKProductRequest來加載可用產品(避免出現購買到無效的 產品)。測試

 

SKProductRequest:產品請求類,主要用於加載產品列表(包括可用產品和不可用產品),一般加載完以後會經過其 -(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response代理方法得到響應,拿到響應中的可用產品。ui

 

SKPayment:產品購買支付類,保存了產品ID、購買數量等信息(注意與其對應的有一個SKMutablePayment對象,此對象能夠修改產品數量等信息)。

 

SKPaymentQueue: 產品購買支付隊列,一旦將一個SKPayment添加到此隊列就會向蘋果服務器發送請求完成這次交易。注意交易的狀態反饋不是經過代理完成的,而是經過一 個交易監聽者(相似於代理,能夠經過隊列的addTransactionObserver來設置)。

 

SKPaymentTransaction: 一次產品購買交易,一般交易完成後支付隊列會調用交易監聽者的-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction方法反饋交易狀況,並在此方法中將交易對象返回。

 

SKStoreProductViewController: 應用程序商店產品展現視圖控制器,用於在應用程序內部展現此應用在應用商店的狀況。(例如可使用它讓用戶在應用內完成評價,注意因爲本次演示的示例程序 沒有正式提交到應用商店,因此在此暫不演示此控制器視圖的使用)。

 

瞭解了以上幾個經常使用的開發API以後,下面看一下應用內購買的流程:

 

  1. 通 過SKProductRequest得到可購買產品SKProduct數組(SKProductRequest會根據程序的Bundle ID去對應的內購配置中獲取指定ID的產品對象),這個過程當中須要知道產品標識(必須和iTuens Connect中的對應起來),能夠存儲到沙盒中也能夠存儲到數據庫中(下面的Demo中定義成了宏定義)。

  2. 請求完成後 能夠在SKProductRequest的-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response代理方法中得到SKProductResponse對象,這個對象中保存了products屬性表示可用產品對象數組。

  3. 給 SKPaymentQueue設置一個監聽者來得到交易的狀態(它相似於一個代理),監聽者經過-(void)paymentQueue: (SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction方法反饋交易的變化狀態(一般在此方法中能夠根據交易成功、恢復成功等狀態來作一些處理)。

  4. 一 旦用戶決定購買某個產品(SKProduct),就能夠根據SKProduct來建立一個對應的支付對象SKPayment,只要將這個對象加入到 SKPaymentQueue中就會觸發購買行爲(將訂單提交到蘋果服務器),一旦一個交易發生變化就會觸發SKPaymentQueue監聽者來反饋交 易狀況。

  5. 交易提交給蘋果服務器以後若是不出意外的話一般就會彈出一個確認購買的對話框,引導用戶完成交易,最終完成交易 後(一般是完成交易,用戶點擊」好「)會調用交易監聽者-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction方法將這次交易的全部交易對象SKPaymentTransaction數組返回,能夠經過交易狀態判斷交易狀況。

  6. 通 常一次交易完成後須要對本次交易進行驗證,避免越獄機器模擬蘋果官方的反饋形成交易成功假象。蘋果官方提供了一個驗證的URL,只要將交易成功後的憑證 (這個憑證從iOS7以後在交易成功會會存儲到沙盒中)傳遞給這個地址就會給出交易狀態和本次交易的詳細信息,經過這些信息(一般能夠根據交易狀態、 Bundler ID、ProductID等確認)能夠標識出交易是否真正完成。

  7. 對於非消耗品,用戶在完成購買後若是用 戶使用其餘機器登陸或者用戶卸載從新安裝應用後一般但願這些非消耗品可以恢復(事實上若是不恢復用戶再次購買也不會成功)。調用 SKPaymentQueue的restoreCompletedTransactions就能夠完成恢復,恢復後會調用交易監聽者的 paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction方法反饋恢復的交易(也就是已購買的非消耗品交易,注意這個過程當中若是沒有非消耗品可恢復,是不會調用此方法的)。

 

下面經過一個示例程序演示內購和恢復的整個過程,程序界面大體以下:

 

主界面中展現了全部可購買產品和售價,以及購買狀況。

 

選擇一個產品點」購買「能夠購買此商品,購買完成後刷新購買狀態(若是是非消耗品則顯示已購買,若是是消耗品則顯示購買個數)。

 

程序卸載後從新安裝能夠點擊」恢復購買「來恢復已購買的非消耗品。

 

130913380424007.png

 

程序代碼:

 

複製代碼
  1 //  2 // KCMainTableViewController.m  3 // kctest  4 //  5 // Created by Kenshin Cui on 14/4/5.  6 // Copyright (c) 2015年 cmjstudio. All rights reserved.  7 //  8 #import "KCMainTableViewController.h"  9 #import  10 #define kAppStoreVerifyURL @"https://buy.itunes.apple.com/verifyReceipt" //實際購買驗證URL  11 #define kSandboxVerifyURL @"https://sandbox.itunes.apple.com/verifyReceipt" //開發階段沙盒驗證URL  12 //定義能夠購買的產品ID,必須和iTunes Connect中設置的一致  13 #define kProductID1 @"ProtectiveGloves" //強力手套,非消耗品  14 #define kProductID2 @"GoldenGlobe" //金球,非消耗品  15 #define kProductID3 @"EnergyBottle" //能量瓶,消耗品  16 @interface KCMainTableViewController ()  17 @property (strong,nonatomic) NSMutableDictionary *products;//有效的產品  18 @property (assign,nonatomic) int selectedRow;//選中行  19 @end  20 @implementation KCMainTableViewController  21 #pragma mark - 控制器視圖方法  22 - (void)viewDidLoad {  23  [super viewDidLoad];  24  [self loadProducts];  25  [self addTransactionObjserver];  26 }  27 #pragma mark - UI事件  28 //購買產品  29 - (IBAction)purchaseClick:(UIBarButtonItem *)sender {  30 NSString *productIdentifier=self.products.allKeys[self.selectedRow];  31 SKProduct *product=self.products[productIdentifier];  32 if (product) {  33  [self purchaseProduct:product];  34 }else{  35 NSLog(@"沒有可用商品.");  36  }  37  38 }  39 //恢復購買  40 - (IBAction)restorePurchaseClick:(UIBarButtonItem *)sender {  41  [self restoreProduct];  42 }  43 #pragma mark - UITableView數據源方法  44 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {  45 return 1;  46 }  47 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {  48 return self.products.count;  49 }  50 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {  51 static NSString *identtityKey=@"myTableViewCellIdentityKey1";  52 UITableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:identtityKey];  53 if(cell==nil){  54 cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identtityKey];  55  }  56 cell.accessoryType=UITableViewCellAccessoryNone;  57 NSString *key=self.products.allKeys[indexPath.row];  58 SKProduct *product=self.products[key];  59 NSString *purchaseString;  60 NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];  61 if ([product.productIdentifier isEqualToString:kProductID3]) {  62 purchaseString=[NSString stringWithFormat:@"已購買%i個",[defaults integerForKey:product.productIdentifier]];  63 }else{  64 if([defaults boolForKey:product.productIdentifier]){  65 purchaseString=@"已購買";  66 }else{  67 purchaseString=@"還沒有購買";  68  }  69  }  70 cell.textLabel.text=[NSString stringWithFormat:@"%@(%@)",product.localizedTitle,purchaseString] ;  71 cell.detailTextLabel.text=[NSString stringWithFormat:@"%@",product.price];  72 return cell;  73 }  74 #pragma mark - UITableView代理方法  75 -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{  76 UITableViewCell *currentSelected=[tableView cellForRowAtIndexPath:indexPath];  77 currentSelected.accessoryType=UITableViewCellAccessoryCheckmark;  78 self.selectedRow=indexPath.row;  79 }  80 -(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{  81 UITableViewCell *currentSelected=[tableView cellForRowAtIndexPath:indexPath];  82 currentSelected.accessoryType=UITableViewCellAccessoryNone;  83 }  84 #pragma mark - SKProductsRequestd代理方法  85 /**  86  * 產品請求完成後的響應方法  87  *  88  * @param request 請求對象  89  * @param response 響應對象,其中包含產品信息  90 */  91 -(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{  92 //保存有效的產品  93 _products=[NSMutableDictionary dictionary];  94 [response.products enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {  95 SKProduct *product=obj;  96  [_products setObject:product forKey:product.productIdentifier];  97  }];  98 //因爲這個過程是異步的,加載成功後從新刷新表格  99  [self.tableView reloadData]; 100 } 101 -(void)requestDidFinish:(SKRequest *)request{ 102 NSLog(@"請求完成."); 103 } 104 -(void)request:(SKRequest *)request didFailWithError:(NSError *)error{ 105 if (error) { 106 NSLog(@"請求過程當中發生錯誤,錯誤信息:%@",error.localizedDescription); 107  } 108 } 109 #pragma mark - SKPaymentQueue監聽方法 110 /** 111  * 交易狀態更新後執行 112  * 113  * @param queue 支付隊列 114  * @param transactions 交易數組,裏面存儲了本次請求的全部交易對象 115 */ 116 -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{ 117 [transactions enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 118 SKPaymentTransaction *paymentTransaction=obj; 119 if (paymentTransaction.transactionState==SKPaymentTransactionStatePurchased){//已購買成功 120 NSLog(@"交易\"%@\"成功.",paymentTransaction.payment.productIdentifier); 121 //購買成功後進行驗證 122  [self verifyPurchaseWithPaymentTransaction]; 123 //結束支付交易 124  [queue finishTransaction:paymentTransaction]; 125 }else if(paymentTransaction.transactionState==SKPaymentTransactionStateRestored){//恢復成功,對於非消耗品才能恢復,若是恢復成功則transaction中記錄的恢復的產品交易 126 NSLog(@"恢復交易\"%@\"成功.",paymentTransaction.payment.productIdentifier); 127 [queue finishTransaction:paymentTransaction];//結束支付交易 128 129 //恢復後從新寫入偏好配置,從新加載UITableView 130  [[NSUserDefaults standardUserDefaults]setBool:YES forKey:paymentTransaction.payment.productIdentifier]; 131  [self.tableView reloadData]; 132 }else if(paymentTransaction.transactionState==SKPaymentTransactionStateFailed){ 133 if (paymentTransaction.error.code==SKErrorPaymentCancelled) {//若是用戶點擊取消 134 NSLog(@"取消購買."); 135  } 136 NSLog(@"ErrorCode:%i",paymentTransaction.error.code); 137  } 138 139  }]; 140 } 141 //恢復購買完成 142 -(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{ 143 NSLog(@"恢復完成."); 144 } 145 #pragma mark - 私有方法 146 /** 147  * 添加支付觀察者監控,一旦支付後則會回調觀察者的狀態更新方法: 148  -(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions 149 */ 150 -(void)addTransactionObjserver{ 151 //設置支付觀察者(相似於代理),經過觀察者來監控購買狀況 152  [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; 153 } 154 /** 155  * 加載全部產品,注意產品必定是從服務器端請求得到,由於有些產品可能開發人員知道其存在性,可是不通過審覈是無效的; 156 */ 157 -(void)loadProducts{ 158 //定義要獲取的產品標識集合 159 NSSet *sets=[NSSet setWithObjects:kProductID1,kProductID2,kProductID3, nil]; 160 //定義請求用於獲取產品 161 SKProductsRequest *productRequest=[[SKProductsRequest alloc]initWithProductIdentifiers:sets]; 162 //設置代理,用於獲取產品加載狀態 163 productRequest.delegate=self; 164 //開始請求 165  [productRequest start]; 166 } 167 /** 168  * 購買產品 169  * 170  * @param product 產品對象 171 */ 172 -(void)purchaseProduct:(SKProduct *)product{ 173 //若是是非消耗品,購買過則提示用戶 174 NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; 175 if ([product.productIdentifier isEqualToString:kProductID3]) { 176 NSLog(@"當前已經購買\"%@\" %i 個.",kProductID3,[defaults integerForKey:product.productIdentifier]); 177 }else if([defaults boolForKey:product.productIdentifier]){ 178 NSLog(@"\"%@\"已經購買過,無需購買!",product.productIdentifier); 179 return; 180  } 181 182 //建立產品支付對象 183 SKPayment *payment=[SKPayment paymentWithProduct:product]; 184 //支付隊列,將支付對象加入支付隊列就造成一次購買請求 185 if (![SKPaymentQueue canMakePayments]) { 186 NSLog(@"設備不支持購買."); 187 return; 188  } 189 SKPaymentQueue *paymentQueue=[SKPaymentQueue defaultQueue]; 190 //添加都支付隊列,開始請求支付 191 // [self addTransactionObjserver]; 192  [paymentQueue addPayment:payment]; 193 } 194 /** 195  * 恢復購買,對於非消耗品若是應用從新安裝或者機器重置後能夠恢復購買 196  * 注意恢復時只能一次性恢復全部非消耗品 197 */ 198 -(void)restoreProduct{ 199 SKPaymentQueue *paymentQueue=[SKPaymentQueue defaultQueue]; 200 //設置支付觀察者(相似於代理),經過觀察者來監控購買狀況 201 // [paymentQueue addTransactionObserver:self]; 202 //恢復全部非消耗品 203  [paymentQueue restoreCompletedTransactions]; 204 } 205 /** 206  * 驗證購買,避免越獄軟件模擬蘋果請求達到非法購買問題 207  * 208 */ 209 -(void)verifyPurchaseWithPaymentTransaction{ 210 //從沙盒中獲取交易憑證而且拼接成請求體數據 211 NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL]; 212 NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl]; 213 214 NSString *receiptString=[receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];//轉化爲base64字符串 215 216 NSString *bodyString = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", receiptString];//拼接請求數據 217 NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding]; 218 //建立請求到蘋果官方進行購買驗證 219 NSURL *url=[NSURL URLWithString:kSandboxVerifyURL]; 220 NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url]; 221 requestM.HTTPBody=bodyData; 222 requestM.HTTPMethod=@"POST"; 223 //建立鏈接併發送同步請求 224 NSError *error=nil; 225 NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error]; 226 if (error) { 227 NSLog(@"驗證購買過程當中發生錯誤,錯誤信息:%@",error.localizedDescription); 228 return; 229  } 230 NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil]; 231 NSLog(@"%@",dic); 232 if([dic[@"status"] intValue]==0){ 233 NSLog(@"購買成功!"); 234 NSDictionary *dicReceipt= dic[@"receipt"]; 235 NSDictionary *dicInApp=[dicReceipt[@"in_app"] firstObject]; 236 NSString *productIdentifier= dicInApp[@"product_id"];//讀取產品標識 237 //若是是消耗品則記錄購買數量,非消耗品則記錄是否購買過 238 NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults]; 239 if ([productIdentifier isEqualToString:kProductID3]) { 240 int purchasedCount=[defaults integerForKey:productIdentifier];//已購買數量 241 [[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier]; 242 }else{ 243  [defaults setBool:YES forKey:productIdentifier]; 244  } 245  [self.tableView reloadData]; 246 //在此處對購買記錄進行存儲,能夠存儲到開發商的服務器端 247 }else{ 248 NSLog(@"購買失敗,未經過驗證!"); 249  } 250 } 251 @end
複製代碼

 

 

運行效果(這是程序在卸載後從新安裝的運行效果,卸載前已經購買」強力手套「,所以程序運行後點擊了」恢復購買「):

 

130914048709643.gif

相關文章
相關標籤/搜索