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

 

posted @ 2015-04-05 19:31  嗷大喵  阅读(715)  评论(0编辑  收藏  举报