IAP内购
IAPHelper.h
// // IAPHelper.h // airplay // // Created by apple on 13-10-23. // Copyright (c) 2013年 itcast. All rights reserved. // #import <Foundation/Foundation.h> typedef void (^myBlock)(); typedef void(^buyCompletionBlock)(NSString *identifier); typedef void(^restoreCompletionBlock)(NSArray *products); typedef void(^failedBlock)(NSString *reason); typedef void (^RequestProductsCompletionHandler)(BOOL success, NSArray * products); @interface IAPHelper : NSObject { NSUserDefaults *defaults; } /** 包装后的IAPHelper的使用方法 1. 调用requestProducts去服务器验证可用的商品列表 2. 调用buyProduct方法,传入要购买的产品标示,并在completion块代码中做后续处理即可 3. 调用restorePurchase方法,并在completion块代码中做后续处理即可 所谓后续处理,就是根据购买情况,调整界面UI或者设置用户属性 提示:在使用IAPHelper的同时,需要导入Base64的两个分类方法。 */ @property(nonatomic,assign)int money; //充值的金额 @property(strong,nonatomic)myBlock block; + (IAPHelper *)sharedIAPHelper; #pragma mark 请求有效产品(使用自定义的产品集合去iTunes服务器确认哪些商品可以销售) - (void)requestProducts:(NSSet *)products; #pragma mark 购买商品(使用指定的产品标示符购买商品) - (void)buyProduct:(NSString *)identifier completion:(buyCompletionBlock)completion failed:(failedBlock)failed; #pragma mark 恢复购买(仅针对非消耗品可用) - (void)restorePurchase:(restoreCompletionBlock)completion failed:(failedBlock)failed; - (void)buyProduct:(NSString *)identifier; @end
IAPHelper.m
// // IAPHelper.m // airplay // // Created by apple on 13-10-23. // Copyright (c) 2013年 itcast. All rights reserved. // #import "IAPHelper.h" #import <StoreKit/StoreKit.h> #import "NSData+Base64.h" static IAPHelper *sharedInstance; /** 为了防止越狱手机插件的拦截,在完成购买之后,需要做购买的验证! */ // 用来真机验证的服务器地址 #define ITMS_PROD_VERIFY_RECEIPT_URL @"https://buy.itunes.apple.com/verifyReceipt" // 开发时模拟器使用的验证服务器地址 #define ITMS_SANDBOX_VERIFY_RECEIPT_URL @"https://sandbox.itunes.apple.com/verifyReceipt" @interface IAPHelper() <SKProductsRequestDelegate, SKPaymentTransactionObserver> { // 从服务器返回的有效商品字典,以备用户购买是使用 NSMutableDictionary *_productDict; // 回调块代码 buyCompletionBlock _buyCompletion; restoreCompletionBlock _restoreCompletion; failedBlock _failedBlock; } @end @implementation IAPHelper #pragma mark - 单例方法 + (id)allocWithZone:(NSZone *)zone { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [super allocWithZone:zone]; // 为共享实例添加交易观察者对象 [[SKPaymentQueue defaultQueue]addTransactionObserver:sharedInstance]; }); return sharedInstance; } + (IAPHelper *)sharedIAPHelper { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[IAPHelper alloc]init]; }); return sharedInstance; } #pragma mark - 内购方法 #pragma mark 请求有效产品(使用自定义的产品集合去iTunes服务器确认哪些商品可以销售) - (void)requestProducts:(NSSet *)products { // 实例化请求 SKProductsRequest *request = [[SKProductsRequest alloc]initWithProductIdentifiers:products]; //NSLog(@"%@",products); // 设置代理 [request setDelegate:self]; // 启动请求 [request start]; } #pragma mark请求错误信息 -(void)request:(SKRequest *)request didFailWithError:(NSError *)error{ NSLog(@"请求错误信息 : %@",error); } #pragma mark请求成功 -(void)requestDidFinish:(SKRequest *)request{ //[activityView stopAnimating]; NSLog(@"success request = %@",request); } #pragma mark 购买商品(使用指定的产品标示符购买商品) - (void)buyProduct:(NSString *)identifier // completion:(buyCompletionBlock)completion // failed:(failedBlock)failed { // 记录回调块代码 // _buyCompletion = completion; // _failedBlock = failed; // 从商品字典中提取商品对象,如果有才购买 // 如果没有,提示用户 SKProduct *product = _productDict[identifier]; if (product) { // 购买 // 1. 实例化付款对象 SKPayment *payment = [SKPayment paymentWithProduct:product]; // 2. 将付款对象添加到付款队列,付款就启动,将购买请求提交给iTunes服务器,等待服务器的相应 [[SKPaymentQueue defaultQueue]addPayment:payment]; } else { // 这种情况会在定义了购买商品,但是苹果没有审批通过,或者苹果服务器不可用时出现 NSLog(@"当前商品不可购买,请稍后再试"); UIAlertView *alterview = [[UIAlertView alloc] initWithTitle:@"充值失败!请稍后再试!" message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:@"确定", nil]; [alterview show]; } } #pragma mark 验证购买 // 验证购买,在每一次购买完成之后,需要对购买的交易进行验证 // 所谓验证,是将交易的凭证进行"加密",POST请求传递给苹果的服务器,苹果服务器对"加密"数据进行验证之后, // 会返回一个json数据,供开发者判断凭据是否有效 // 有些“内购助手”同样会拦截验证凭据,返回一个伪造的验证结果 // 所以在开发时,对凭据的检验要格外小心 - (void)verifyPurchase:(SKPaymentTransaction *)transaction { // 使用base64的加密算法,对凭据进行加密处理 // 1. 使用base64加密交易凭据 NSString *encodeStr = [transaction.transactionReceipt base64EncodedString]; // 2. 建立验证请求 // 1) 测试的URL ITMS_PROD_VERIFY_RECEIPT_URL NSURL *url = [NSURL URLWithString:ITMS_PROD_VERIFY_RECEIPT_URL]; // 2) 建立请求 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f]; // 1> 请求数据体 NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr]; NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; // 2> 设置数据体 [request setHTTPBody:payloadData]; // 3> 设置请求方法 [request setHTTPMethod:@"POST"]; // 3) 建立连接,发送同步请求! // 不能发送异步请求!后续还要对服务器返回结果做进一步的确认,以保证用户真的是在购买! // 所谓真的购买,不是插件模拟的校验数据 NSURLResponse *response = nil; // 此请求返回的是一个json结果 NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil]; // 将数据反序列化为数据字典 if (data == nil) { return; } NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]; if (jsonDict != nil) { [[NSNotificationCenter defaultCenter]postNotificationName:KJOINMEMBERNOTIFICATIONCENTER object:nil]; } // 针对服务器返回数据进行校验 // 通常需要校验:bid,product_id,purchase_date,status // if ([jsonDict[@"status"]integerValue] == 0) { // _buyCompletion(transaction.payment.productIdentifier); // } else { // _buyCompletion(@"验证失败,检查你的机器是否越狱"); // } } #pragma mark 恢复购买(仅针对非消耗品可用) // 恢复购买的应用场景 // 1) 用户在其他设备上恢复非消耗品的购买 // 2) 用户的手机恢复出厂设置,或者重新安装软件之后,可以使用恢复购买 // 提示:恢复购买本质上和采购非常像,对于非消耗品而言,即便是再次采购,也不会让用户付费 // 恢复购买相对更加人性化一些,因此,在实际开发中,两个按钮一个都不能少 // 使用恢复购买,可以恢复用户已经购买的所有非消耗品类型的商品 - (void)restorePurchase:(restoreCompletionBlock)completion failed:(failedBlock)failed { // 记录回调块代码 _restoreCompletion = completion; _failedBlock = failed; // 恢复购买的工作原理,使用用户的appleID连接到itunes服务器,检查用户曾经购买的所有商品 // 将商品集合返回给用户 [[SKPaymentQueue defaultQueue]restoreCompletedTransactions]; } #pragma mark SKProductsRequest Delegate - (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response { // 懒加载产品字典 if (_productDict == nil) { _productDict = [NSMutableDictionary dictionaryWithCapacity:response.products.count]; } else { [_productDict removeAllObjects]; } NSLog(@"有效的产品列表 %@",response.products); NSLog(@"无效的商品:%@",response.invalidProductIdentifiers); // 遍历服务器返回的产品列表 for (SKProduct *product in response.products) { // 输出有效产品(当前可以购买的产品)唯一标示符 // NSLog(@"////%@", product.productIdentifier); // 需要记录服务器返回的有效商品,以便后续的购买 // 提示:不要直接使用自定义的商品标示符开始购买,购买前,一定要从服务器查询可用商品 // 以免服务器调整或其他原因,用户无法正常采购,同时造成金钱的损失 [_productDict setObject:product forKey:product.productIdentifier]; } } #pragma mark - 交易观察者方法 // 付款队列中的交易变化的回调方法 - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { // 针对恢复操作,定义一个临时数组 //NSMutableArray *restoreArray = [NSMutableArray arrayWithCapacity:transactions.count]; // 判断是否是恢复的操作 //BOOL isRestore = NO; for (SKPaymentTransaction *transaction in transactions) { //NSLog(@"transaction.State = %@",transaction); switch (transaction.transactionState) { case SKPaymentTransactionStatePurchased://交易完成 break; case SKPaymentTransactionStateFailed://交易失败 //[self failedTransaction:transaction]; break; case SKPaymentTransactionStateRestored://已经购买过该商品 // [self restoreTransaction:transaction]; break; case SKPaymentTransactionStatePurchasing: //商品添加进列表 NSLog(@"商品添加进列表"); break; default: break; } } for (SKPaymentTransaction *transction in transactions) { // 如果交易的状态是购买完成,说明商品购买成功 if (SKPaymentTransactionStatePurchased == transction.transactionState) { NSLog(@"购买成功 %@", transction.payment.productIdentifier); // 验证凭据 if (CurrentSystemVersion >= 7) { [self verifyPruchaseIOS7]; }else { [self verifyPurchase:transction]; } //[self verifyFinishedTransaction:transction]; // 通知队列结束交易 [queue finishTransaction:transction]; } // else if (SKPaymentTransactionStateRestored == transction.transactionState) { // isRestore = YES; // // // 恢复购买 // [restoreArray addObject:transction.payment.productIdentifier]; // // // 通知队列结束交易 // [queue finishTransaction:transction]; // } else if (SKPaymentTransactionStateFailed == transction.transactionState) { // // 判断是否因为用户点击取消,产生的请求失败 // if (SKErrorPaymentCancelled != transction.error.code) { // // 出错块代码回调,调用回调方法之前,需要判断回调方法是否设置 // // 如此设置之后,可以给回调方法设置为nil,否则会报错! // if (_failedBlock) { // _failedBlock(transction.error.localizedDescription); // } // } // } } // 如果是恢复的交易 // if (isRestore) { // // 调用块代码回传整个恢复的产品标示数组 // _restoreCompletion(restoreArray); // } } - (void)verifyPruchaseIOS7 { // 验证凭据,获取到苹果返回的交易凭据 // appStoreReceiptURL iOS7.0增加的,购买交易完成后,会将凭据存放在该地址 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; // 从沙盒中获取到购买凭据 NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL]; // 发送网络POST请求,对购买凭据进行验证 NSURL *url = [NSURL URLWithString:ITMS_PROD_VERIFY_RECEIPT_URL]; // 国内访问苹果服务器比较慢,timeoutInterval需要长一点 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0f]; request.HTTPMethod = @"POST"; // 在网络中传输数据,大多情况下是传输的字符串而不是二进制数据 // 传输的是BASE64编码的字符串 /** BASE64 常用的编码方案,通常用于数据传输,以及加密算法的基础算法,传输过程中能够保证数据传输的稳定性 BASE64是可以编码和解码的 */ NSString *encodeStr = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\"}", encodeStr]; NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding]; request.HTTPBody = payloadData; // 提交验证请求,并获得官方的验证JSON结果 NSData *result = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]; // 官方验证结果为空 if (result == nil) { NSLog(@"验证失败"); } NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingAllowFragments error:nil]; NSLog(@"%@", dict); if (dict != nil) { // 比对字典中以下信息基本上可以保证数据安全 // bundle_id&application_version&product_id&transaction_id [[NSNotificationCenter defaultCenter]postNotificationName:KJOINMEMBERNOTIFICATIONCENTER object:nil]; NSLog(@"验证成功"); } } @end