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

相关文章
相关标签/搜索