内购

大家都知道做iOS开发本身的收入有三种来源:出售应用、内购和广告。国内用户通常很少直接购买应用,因此对于开发者而言(特别是个人开发者),内购和广告收入就成了主要的收入来源。内购营销模式,通常软件本身是不收费的,但是要获得某些特权就必须购买一些道具,而内购的过程是由苹果官方统一来管理的,所以和Game Center一样,在开发内购程序之前要做一些准备工作(下面的准备工作主要是针对真机的,模拟器省略Provisioning Profile配置过程):

  1. 前四步和Game Center基本完全一致,只是在选择服务时不是选择Game Center而是要选择内购服务(In-App Purchase)。
  2. 到iTuens Connect中设置“App 内购买项目”,这里仍然以上面的“KCTest”项目为例,假设这个足球竞技游戏中有三种道具,分别为“强力手套”(增强防御)、“金球”(增加金球率)和“能量瓶”(提供足够体力),前两者是非消耗品只用一次性购买,后者是消耗品用完一次必须再次购买。
  3. 到iTunes Connect中找到“协议、税务和银行业务”增加“iOS Paid Applications”协议,并完成所有配置后等待审核通过(注意这一步如果不设置在应用程序中无法获得可购买产品)。
  4. 在iOS“设置”中找到”iTunes Store与App Store“,在这里可以选择使用沙盒用户登录或者处于注销状态,但是一定注意不能使用真实用户登录,否则下面的购买测试不会成功,因为到目前为止我们的应用并没有真正通过苹果官方审核只能用沙盒测试用户(如果是模拟器不需要此项设置)。
  5. 有了上面的设置之后保证应用程序Bundle ID和iTunes Connect中的Bundle ID(或者说App ID中配置的Bundle ID)一致即可准备开发。

开发内购应用时需要使用StoreKit.framework,下面是这个框架中常用的几个类:

SKProduct:可购买的产品(例如上面设置的能量瓶、强力手套等),其productIdentifier属性对应iTunes Connect中配置的“产品ID“,但是此类不建议直接初始化使用,而是要通过SKProductRequest来加载可用产品(避免出现购买到无效的产品)。

SKProductRequest:产品请求类,主要用于加载产品列表(包括可用产品和不可用产品),通常加载完之后会通过其-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response代理方法获得响应,拿到响应中的可用产品。

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方法反馈恢复的交易(也就是已购买的非消耗品交易,注意这个过程中如果没有非消耗品可恢复,是不会调用此方法的)。

下面通过一个示例程序演示内购和恢复的整个过程,程序界面大致如下:

主界面中展示了所有可购买产品和售价,以及购买情况。

选择一个产品点”购买“可以购买此商品,购买完成后刷新购买状态(如果是非消耗品则显示已购买,如果是消耗品则显示购买个数)。

程序卸载后重新安装可以点击”恢复购买“来恢复已购买的非消耗品。

程序代码:

<span style="color: green;">//
//  KCMainTableViewController.m
//  kctest
//
//  Created by Kenshin Cui on 14/4/5.
//  Copyright (c) 2015年 cmjstudio. All rights reserved.
//</span><span style="color: blue;">#import </span><span style="color: rgb(163, 21, 21);">"KCMainTableViewController.h"
</span><span style="color: blue;">#import </span><span style="color: rgb(163, 21, 21);"><StoreKit/StoreKit.h>
</span><span style="color: blue;">#define </span><span style="color: black;">kAppStoreVerifyURL @</span><span style="color: rgb(163, 21, 21);">"https://buy.itunes.apple.com/verifyReceipt" </span><span style="color: green;">//实际购买验证URL
</span><span style="color: blue;">#define </span><span style="color: black;">kSandboxVerifyURL @</span><span style="color: rgb(163, 21, 21);">"https://sandbox.itunes.apple.com/verifyReceipt" </span><span style="color: green;">//开发阶段沙盒验证URL//定义可以购买的产品ID,必须和iTunes Connect中设置的一致
</span><span style="color: blue;">#define </span><span style="color: black;">kProductID1 @</span><span style="color: rgb(163, 21, 21);">"ProtectiveGloves" </span><span style="color: green;">//强力手套,非消耗品
</span><span style="color: blue;">#define </span><span style="color: black;">kProductID2 @</span><span style="color: rgb(163, 21, 21);">"GoldenGlobe" </span><span style="color: green;">//金球,非消耗品
</span><span style="color: blue;">#define </span><span style="color: black;">kProductID3 @</span><span style="color: rgb(163, 21, 21);">"EnergyBottle" </span><span style="color: green;">//能量瓶,消耗品</span><span style="color: black;">@</span><span style="color: blue;">interface </span><span style="color: black;">KCMainTableViewController ()<SKProductsRequestDelegate,SKPaymentTransactionObserver>@</span><span style="color: blue;">property </span><span style="color: black;">(strong,nonatomic) NSMutableDictionary *products;</span><span style="color: green;">//有效的产品
</span><span style="color: black;">@</span><span style="color: blue;">property </span><span style="color: black;">(assign,nonatomic) </span><span style="color: blue;">int </span><span style="color: black;">selectedRow;</span><span style="color: green;">//选中行
</span><span style="color: black;">@end@implementation KCMainTableViewController
</span><span style="color: blue;">#pragma </span><span style="color: black;">mark - 控制器视图方法
- (</span><span style="color: blue;">void</span><span style="color: black;">)viewDidLoad {[super viewDidLoad];[self loadProducts];[self addTransactionObjserver];
}</span><span style="color: blue;">#pragma </span><span style="color: black;">mark - UI事件
</span><span style="color: green;">//购买产品
</span><span style="color: black;">- (IBAction)purchaseClick:(UIBarButtonItem *)sender {NSString *productIdentifier=self.products.allKeys[self.selectedRow];SKProduct *product=self.products[productIdentifier];</span><span style="color: blue;">if </span><span style="color: black;">(product) {[self purchaseProduct:product];}</span><span style="color: blue;">else</span><span style="color: black;">{NSLog(@</span><span style="color: rgb(163, 21, 21);">"没有可用商品."</span><span style="color: black;">);}}
</span><span style="color: green;">//恢复购买
</span><span style="color: black;">- (IBAction)restorePurchaseClick:(UIBarButtonItem *)sender {[self restoreProduct];
}</span><span style="color: blue;">#pragma </span><span style="color: black;">mark - UITableView数据源方法- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {</span><span style="color: blue;">return </span><span style="color: black;">1;
}- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {</span><span style="color: blue;">return </span><span style="color: black;">self.products.count;
}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {</span><span style="color: blue;">static </span><span style="color: black;">NSString *identtityKey=@</span><span style="color: rgb(163, 21, 21);">"myTableViewCellIdentityKey1"</span><span style="color: black;">;UITableViewCell *cell=[self.tableView dequeueReusableCellWithIdentifier:identtityKey];</span><span style="color: blue;">if</span><span style="color: black;">(cell==nil){cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:identtityKey];}cell.accessoryType=UITableViewCellAccessoryNone;NSString *key=self.products.allKeys[indexPath.row];SKProduct *product=self.products[key];NSString *purchaseString;NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];</span><span style="color: blue;">if </span><span style="color: black;">([product.productIdentifier isEqualToString:kProductID3]) {purchaseString=[NSString stringWithFormat:@</span><span style="color: rgb(163, 21, 21);">"已购买%i个"</span><span style="color: black;">,[defaults integerForKey:product.productIdentifier]];}</span><span style="color: blue;">else</span><span style="color: black;">{</span><span style="color: blue;">if</span><span style="color: black;">([defaults boolForKey:product.productIdentifier]){purchaseString=@</span><span style="color: rgb(163, 21, 21);">"已购买"</span><span style="color: black;">;}</span><span style="color: blue;">else</span><span style="color: black;">{purchaseString=@</span><span style="color: rgb(163, 21, 21);">"尚未购买"</span><span style="color: black;">;}}cell.textLabel.text=[NSString stringWithFormat:@</span><span style="color: rgb(163, 21, 21);">"%@(%@)"</span><span style="color: black;">,product.localizedTitle,purchaseString] ;cell.detailTextLabel.text=[NSString stringWithFormat:@</span><span style="color: rgb(163, 21, 21);">"%@"</span><span style="color: black;">,product.price];</span><span style="color: blue;">return </span><span style="color: black;">cell;
}
</span><span style="color: blue;">#pragma </span><span style="color: black;">mark - UITableView代理方法
-(</span><span style="color: blue;">void</span><span style="color: black;">)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{UITableViewCell *currentSelected=[tableView cellForRowAtIndexPath:indexPath];currentSelected.accessoryType=UITableViewCellAccessoryCheckmark;self.selectedRow=indexPath.row;
}
-(</span><span style="color: blue;">void</span><span style="color: black;">)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{UITableViewCell *currentSelected=[tableView cellForRowAtIndexPath:indexPath];currentSelected.accessoryType=UITableViewCellAccessoryNone;
}</span><span style="color: blue;">#pragma </span><span style="color: black;">mark - SKProductsRequestd代理方法
</span><span style="color: green;">/***  产品请求完成后的响应方法**  @param request  请求对象*  @param response 响应对象,其中包含产品信息*/
</span><span style="color: black;">-(</span><span style="color: blue;">void</span><span style="color: black;">)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{</span><span style="color: green;">//保存有效的产品</span><span style="color: black;">_products=[NSMutableDictionary dictionary];[response.products enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {SKProduct *product=obj;[_products setObject:product forKey:product.productIdentifier];}];</span><span style="color: green;">//由于这个过程是异步的,加载成功后重新刷新表格</span><span style="color: black;">[self.tableView reloadData];
}
-(</span><span style="color: blue;">void</span><span style="color: black;">)requestDidFinish:(SKRequest *)request{NSLog(@</span><span style="color: rgb(163, 21, 21);">"请求完成."</span><span style="color: black;">);
}
-(</span><span style="color: blue;">void</span><span style="color: black;">)request:(SKRequest *)request didFailWithError:(NSError *)error{</span><span style="color: blue;">if </span><span style="color: black;">(error) {NSLog(@</span><span style="color: rgb(163, 21, 21);">"请求过程中发生错误,错误信息:%@"</span><span style="color: black;">,error.localizedDescription);}
}</span><span style="color: blue;">#pragma </span><span style="color: black;">mark - SKPaymentQueue监听方法
</span><span style="color: green;">/***  交易状态更新后执行**  @param queue        支付队列*  @param transactions 交易数组,里面存储了本次请求的所有交易对象*/
</span><span style="color: black;">-(</span><span style="color: blue;">void</span><span style="color: black;">)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{[transactions enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {SKPaymentTransaction *paymentTransaction=obj;</span><span style="color: blue;">if </span><span style="color: black;">(paymentTransaction.transactionState==SKPaymentTransactionStatePurchased){</span><span style="color: green;">//已购买成功</span><span style="color: black;">NSLog(@</span><span style="color: rgb(163, 21, 21);">"交易\"%@\"成功."</span><span style="color: black;">,paymentTransaction.payment.productIdentifier);</span><span style="color: green;">//购买成功后进行验证</span><span style="color: black;">[self verifyPurchaseWithPaymentTransaction];</span><span style="color: green;">//结束支付交易</span><span style="color: black;">[queue finishTransaction:paymentTransaction];}</span><span style="color: blue;">else if</span><span style="color: black;">(paymentTransaction.transactionState==SKPaymentTransactionStateRestored){</span><span style="color: green;">//恢复成功,对于非消耗品才能恢复,如果恢复成功则transaction中记录的恢复的产品交易</span><span style="color: black;">NSLog(@</span><span style="color: rgb(163, 21, 21);">"恢复交易\"%@\"成功."</span><span style="color: black;">,paymentTransaction.payment.productIdentifier);[queue finishTransaction:paymentTransaction];</span><span style="color: green;">//结束支付交易//恢复后重新写入偏好配置,重新加载UITableView</span><span style="color: black;">[[NSUserDefaults standardUserDefaults]setBool:YES forKey:paymentTransaction.payment.productIdentifier];[self.tableView reloadData];}</span><span style="color: blue;">else if</span><span style="color: black;">(paymentTransaction.transactionState==SKPaymentTransactionStateFailed){</span><span style="color: blue;">if </span><span style="color: black;">(paymentTransaction.error.code==SKErrorPaymentCancelled) {</span><span style="color: green;">//如果用户点击取消</span><span style="color: black;">NSLog(@</span><span style="color: rgb(163, 21, 21);">"取消购买."</span><span style="color: black;">);}NSLog(@</span><span style="color: rgb(163, 21, 21);">"ErrorCode:%i"</span><span style="color: black;">,paymentTransaction.error.code);}}];
}
</span><span style="color: green;">//恢复购买完成
</span><span style="color: black;">-(</span><span style="color: blue;">void</span><span style="color: black;">)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{NSLog(@</span><span style="color: rgb(163, 21, 21);">"恢复完成."</span><span style="color: black;">);
}</span><span style="color: blue;">#pragma </span><span style="color: black;">mark - 私有方法
</span><span style="color: green;">/***  添加支付观察者监控,一旦支付后则会回调观察者的状态更新方法:-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions*/
</span><span style="color: black;">-(</span><span style="color: blue;">void</span><span style="color: black;">)addTransactionObjserver{</span><span style="color: green;">//设置支付观察者(类似于代理),通过观察者来监控购买情况</span><span style="color: black;">[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
</span><span style="color: green;">/***  加载所有产品,注意产品一定是从服务器端请求获得,因为有些产品可能开发人员知道其存在性,但是不经过审核是无效的;*/
</span><span style="color: black;">-(</span><span style="color: blue;">void</span><span style="color: black;">)loadProducts{</span><span style="color: green;">//定义要获取的产品标识集合</span><span style="color: black;">NSSet *sets=[NSSet setWithObjects:kProductID1,kProductID2,kProductID3, nil];</span><span style="color: green;">//定义请求用于获取产品</span><span style="color: black;">SKProductsRequest *productRequest=[[SKProductsRequest alloc]initWithProductIdentifiers:sets];</span><span style="color: green;">//设置代理,用于获取产品加载状态</span><span style="color: black;">productRequest.</span><span style="color: blue;">delegate</span><span style="color: black;">=self;</span><span style="color: green;">//开始请求</span><span style="color: black;">[productRequest start];
}
</span><span style="color: green;">/***  购买产品**  @param product 产品对象*/
</span><span style="color: black;">-(</span><span style="color: blue;">void</span><span style="color: black;">)purchaseProduct:(SKProduct *)product{</span><span style="color: green;">//如果是非消耗品,购买过则提示用户</span><span style="color: black;">NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];</span><span style="color: blue;">if </span><span style="color: black;">([product.productIdentifier isEqualToString:kProductID3]) {NSLog(@</span><span style="color: rgb(163, 21, 21);">"当前已经购买\"%@\" %i 个."</span><span style="color: black;">,kProductID3,[defaults integerForKey:product.productIdentifier]);}</span><span style="color: blue;">else if</span><span style="color: black;">([defaults boolForKey:product.productIdentifier]){NSLog(@</span><span style="color: rgb(163, 21, 21);">"\"%@\"已经购买过,无需购买!"</span><span style="color: black;">,product.productIdentifier);</span><span style="color: blue;">return</span><span style="color: black;">;}</span><span style="color: green;">//创建产品支付对象</span><span style="color: black;">SKPayment *payment=[SKPayment paymentWithProduct:product];</span><span style="color: green;">//支付队列,将支付对象加入支付队列就形成一次购买请求</span><span style="color: blue;">if </span><span style="color: black;">(![SKPaymentQueue canMakePayments]) {NSLog(@</span><span style="color: rgb(163, 21, 21);">"设备不支持购买."</span><span style="color: black;">);</span><span style="color: blue;">return</span><span style="color: black;">;}SKPaymentQueue *paymentQueue=[SKPaymentQueue defaultQueue];</span><span style="color: green;">//添加都支付队列,开始请求支付
//    [self addTransactionObjserver];</span><span style="color: black;">[paymentQueue addPayment:payment];
}</span><span style="color: green;">/***  恢复购买,对于非消耗品如果应用重新安装或者机器重置后可以恢复购买*  注意恢复时只能一次性恢复所有非消耗品*/
</span><span style="color: black;">-(</span><span style="color: blue;">void</span><span style="color: black;">)restoreProduct{SKPaymentQueue *paymentQueue=[SKPaymentQueue defaultQueue];</span><span style="color: green;">//设置支付观察者(类似于代理),通过观察者来监控购买情况
//    [paymentQueue addTransactionObserver:self];//恢复所有非消耗品</span><span style="color: black;">[paymentQueue restoreCompletedTransactions];
}</span><span style="color: green;">/***  验证购买,避免越狱软件模拟苹果请求达到非法购买问题**/
</span><span style="color: black;">-(</span><span style="color: blue;">void</span><span style="color: black;">)verifyPurchaseWithPaymentTransaction{</span><span style="color: green;">//从沙盒中获取交易凭证并且拼接成请求体数据</span><span style="color: black;">NSURL *receiptUrl=[[NSBundle mainBundle] appStoreReceiptURL];NSData *receiptData=[NSData dataWithContentsOfURL:receiptUrl];NSString *receiptString=[receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];</span><span style="color: green;">//转化为base64字符串</span><span style="color: black;">NSString *bodyString = [NSString stringWithFormat:@</span><span style="color: rgb(163, 21, 21);">"{\"receipt-data\" : \"%@\"}"</span><span style="color: black;">, receiptString];</span><span style="color: green;">//拼接请求数据</span><span style="color: black;">NSData *bodyData = [bodyString dataUsingEncoding:NSUTF8StringEncoding];</span><span style="color: green;">//创建请求到苹果官方进行购买验证</span><span style="color: black;">NSURL *url=[NSURL URLWithString:kSandboxVerifyURL];NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url];requestM.HTTPBody=bodyData;requestM.HTTPMethod=@</span><span style="color: rgb(163, 21, 21);">"POST"</span><span style="color: black;">;</span><span style="color: green;">//创建连接并发送同步请求</span><span style="color: black;">NSError *error=nil;NSData *responseData=[NSURLConnection sendSynchronousRequest:requestM returningResponse:nil error:&error];</span><span style="color: blue;">if </span><span style="color: black;">(error) {NSLog(@</span><span style="color: rgb(163, 21, 21);">"验证购买过程中发生错误,错误信息:%@"</span><span style="color: black;">,error.localizedDescription);</span><span style="color: blue;">return</span><span style="color: black;">;}NSDictionary *dic=[NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingAllowFragments error:nil];NSLog(@</span><span style="color: rgb(163, 21, 21);">"%@"</span><span style="color: black;">,dic);</span><span style="color: blue;">if</span><span style="color: black;">([dic[@</span><span style="color: rgb(163, 21, 21);">"status"</span><span style="color: black;">] intValue]==0){NSLog(@</span><span style="color: rgb(163, 21, 21);">"购买成功!"</span><span style="color: black;">);NSDictionary *dicReceipt= dic[@</span><span style="color: rgb(163, 21, 21);">"receipt"</span><span style="color: black;">];NSDictionary *dicInApp=[dicReceipt[@</span><span style="color: rgb(163, 21, 21);">"in_app"</span><span style="color: black;">] firstObject];NSString *productIdentifier= dicInApp[@</span><span style="color: rgb(163, 21, 21);">"product_id"</span><span style="color: black;">];</span><span style="color: green;">//读取产品标识//如果是消耗品则记录购买数量,非消耗品则记录是否购买过</span><span style="color: black;">NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];</span><span style="color: blue;">if </span><span style="color: black;">([productIdentifier isEqualToString:kProductID3]) {</span><span style="color: blue;">int </span><span style="color: black;">purchasedCount=[defaults integerForKey:productIdentifier];</span><span style="color: green;">//已购买数量</span><span style="color: black;">[[NSUserDefaults standardUserDefaults] setInteger:(purchasedCount+1) forKey:productIdentifier];}</span><span style="color: blue;">else</span><span style="color: black;">{[defaults setBool:YES forKey:productIdentifier];}[self.tableView reloadData];</span><span style="color: green;">//在此处对购买记录进行存储,可以存储到开发商的服务器端</span><span style="color: black;">}</span><span style="color: blue;">else</span><span style="color: black;">{NSLog(@</span><span style="color: rgb(163, 21, 21);">"购买失败,未通过验证!"</span><span style="color: black;">);}
}
@end</span>

运行效果(这是程序在卸载后重新安装的运行效果,卸载前已经购买”强力手套“,因此程序运行后点击了”恢复购买“):

出自ios深入浅出专栏(内购)相关推荐

  1. iOS:苹果内购实践

    iOS 苹果的内购 一.介绍 苹果规定,凡是虚拟的物品(例如:QQ音乐的乐币)进行交易时,都必须走苹果的内购通道,苹果要收取大约30%的抽成,所以不允许接入第三方的支付方式(微信.支付宝等),当然开发 ...

  2. 关于iOS订阅型内购开发

    ####由于公司项目里面有一个类似购买一个时期的产品,原本使用消耗式内购来做,但是被苹果审核拒绝了,苹果建议(要求)使用订阅式内购来做这个,于是就来研究一下 #####1.第一步添加内购产品 首先还是 ...

  3. 1、ios开发之 内购

    大家都知道做iOS开发本身的收入有三种来源:出售应用.内购和广告.国内用户通常很少直接购买应用,因此对于开发者而言(特别是个人开发者),内购和广告收入就成了主要的收入来源.内购营销模式,通常软件本身是 ...

  4. iOS IAP应用内购详细步骤和问题总结指南

    最近公司在做APP内购会员功能 遇到了很多问题 总结记录一下 首先一定要区分Apple pay 和IAP内购的区别 可以先去看一下官方文档地址 有每个步骤的详细解释 本篇文章分为:1. 内购支付流程: ...

  5. iOS通讯录,蓝牙,内购等开发系列

    –系统应用与系统服务 iOS开发过程中有时候难免会使用iOS内置的一些应用软件和服务,例如QQ通讯录.微信电话本会使用iOS的通讯录,一些第三方软件会在应用内发送短信等.今天将和大家一起学习如何使用系 ...

  6. IOS OC IPA内购流程

    IOS 内购分为四种商品类型: 消耗品项目 非消耗品项目 自动续期订阅 非续期订阅 基本实现流程 添加支付监听 [[SKPaymentQueue defaultQueue] addTransactio ...

  7. 【iOS】苹果内购调研

    参考文章 官方文档 iOS开发内购全套图文教程 App Store上架指导 苹果不允许 iOS 应用内置购买(IAP)使用第三方支付方式,那么跨平台的电子书阅读器怎么解决这个问题? 应用内购(In-A ...

  8. [iOS]swift版内购

    //内购Demo,看代码说话吧 class IAPTestViewController: UIViewController ,SKProductsRequestDelegate, SKPaymentT ...

  9. iOS开发支付 — 内购(IAP)

    为什么要使用内购? 如果你购买的商品,是在本App中使用和消耗的,就一定要用内购,否则会被拒绝上线,例如:游戏币.在线书籍.直播中用来打赏用的金币.app中使用的道具等.如果是直接购买商城之类的快递包 ...

最新文章

  1. GNU screen 命令
  2. 代码农民从做事情的经验
  3. 浅谈虚拟化技术下的云安全如何处置
  4. js中函数声明先提升还是变量先提升
  5. 开发工具:Git和SVN有哪些差异,看完你就懂了?
  6. greenev —— Python 异步网络服务框架
  7. 运维网发布的nagioscacti配置文档
  8. 08.Prevent exceptions from leaving destructors
  9. ios手机上java最好的编辑器_程序员编程利器:20款最好的免费的IDEs和编辑器
  10. SWUST OJ533你的QQ多少级了?
  11. 畅邮(DM Pro)-一款强悍、纯净而稳定的重量级电子邮箱客户端(支持分发、追踪)...
  12. 小米MAX开发者选项 以及如何连接MAC开发RN
  13. 2.Python环境搭建
  14. 计算机研究生考试题,全国研究生考试计算机统考试题及答案-20210522014129.docx-原创力文档...
  15. Mel spectrum梅尔频谱与MFCCs
  16. 三类机构舆情-2019年3月5日
  17. Tensorflow训练模型越来越慢
  18. 实现导入Excel表导入数据库并显示在echarts图形上面
  19. RISC-V ELF规范和函数调用规范
  20. 高频功率放大器工作原理总结(高频和低频功率放大器的区别)

热门文章

  1. 快速记忆单词,一年考过1级(转)
  2. DAO(DBUtils实现)
  3. Ubuntu 9.10 更新源(ubuntu yuan)
  4. apicloud (第五篇 bmap百度地图一键回到当前位置)
  5. 西门子博途v16系统要求_西门子正式发布博途V16(内含百度云盘下载地址)
  6. HMS Core Drive SDK构建使用华为云空间服务应用程序
  7. box-shadow的使用
  8. 牛顿迭代法解线性方程matlab程序,牛顿迭代法matlab程序(解线性方程组)
  9. 名企笔试真题精选 (四)
  10. mysql 取前几分钟和几秒,mysql 数据库取前后几秒 几分钟 几小时 几天的语句