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
浙公网安备 33010602011771号