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

IAP内购的更多相关文章

  1. JAVA项目之苹果IAP内购JAVA服务器验证流程详解

    1.前言 本博客是经历过多个项目检验的, 绝对真实, 适应于对苹果iap内购稍微有些了解的JAVA开发人员,  认真看,  定能完美解决苹果内购问题. 苹果IAP内购支付实际上是"将客户端支 ...

  2. ios IAP 内购验证

    参考我之前的笔记 苹果内购笔记,在客户端向苹果购买成功之后,我们需要进行二次验证. 二次验证 IOS在沙箱环境下购买成功之后,向苹果进行二次验证,确认用户是否购买成功. 当应用向Apple服务器请求购 ...

  3. IAP内购 返回的产品数量为0

    上个月搞IAP,提交到appstore审核被拒,根据附件截图 可以知道是请求产品信息的时候,产品数量返回0了. 返回产品数量为0 要么是Itunes Connect 里面的Contracts Tax ...

  4. iOS开发苹果内购的介绍与实现

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

  5. IAP 程序内购

    最近用到IAP内置购买,阅读官方文档,在网上找了些资料,在这里作下整理,以便日后查找和修改,主要流程方向确定,文档和相关转载内容截图不一一指出,google一堆. 1.查找官方文档,两张目录截图,对主 ...

  6. iOS开发支付篇-内购(IAP)

    一,前言 经典文章参考: . http://yimouleng.com/2015/12/17/ios-AppStore/ 内购流程 . http://www.jianshu.com/p/b199a46 ...

  7. Unity苹果(iOS)内购接入(Unity内置IAP)

    https://www.jianshu.com/p/4045ebf81a1c Unity苹果(iOS)内购接入(Unity内置IAP) Kakarottog                       ...

  8. [Xcode 实际操作]九、实用进阶-(29)为App添加IAP(支付方式)内购项目

    目录:[Swift]Xcode实际操作 首先请阅读:[Xcode10 实际操作]九.实用进阶-(28)在iTunes Connect(苹果商店的管理后台)中创建一个新的新的APP 本文将演示如何给刚刚 ...

  9. [Xcode 实际操作]九、实用进阶-(30)为IAP(支付方式)内购项目添加测试账号,测试内购功能

    目录:[Swift]Xcode实际操作 本文将演示如何添加测试账号,以方便对内购功能进行测试. IAP,即in-App Purchase ,是一种智能移动终端应用程序付费的模式, 在苹果(Apple) ...

随机推荐

  1. DOM对象常用对象的方法和属性

      HTML文档中的常用节点类型: 接口 nodeType 备注 Element 1 元素节点 Text 3 文本节点 Document 9 Document Comment 8 注释文本 Docum ...

  2. SELinux Mysql的error-log文件位置的指定

    SELinux下,在配置my.cnf时,必须指定error-log的位置在/var/log/下, 否则error的默认位置为例如 /var/lib/mysql下的tyoyi.server.err文件, ...

  3. postgresql pg_hba.conf

    pg_hba.conf是客户端认证配置文件 METHOD指定如何处理客户端的认证.常用的有ident,md5,password,trust,reject. PostgreSQL默认只监听本地端口,用n ...

  4. A. Puzzles CodeForces Round #196 (Div.2)

    题目的大意是,给你 m 个数字,让你从中选 n 个,使得选出的数字的极差最小. 好吧,超级大水题.因为要极差最小,所以当然想到要排个序咯,然后去连续的 n 个数字,因为数据不大,所以排完序之后直接暴力 ...

  5. 用list<类>集合接收一个网址返回的一个类的集合的XML

    JavaScriptSerializer serializer = new JavaScriptSerializer(); string json = Share.Helper.HttpRequest ...

  6. openlayers加载地图没有图片时有红叉的解决方法

    解决方式:设置样式隐藏图片 <style type="text/css"> .olImageLoadError { /*ol2.12 onImageLoadError ...

  7. 页面异步加载javascript文件

    昨天听一同事说的异步加载js文件,可以提高页面加载速度.具体方法如下:(function() {  var ga = document.createElement('script'); ga.type ...

  8. redmine 2.5.2 安装后邮件无法发送

    找到redmine安装目录-apps->redmine->htdocs->conflg->configuration.yml email_delivery:    delive ...

  9. 【C++】统计代码覆盖率(三)

    报告集成到jenkins才是最终目的,因此又进行了部分资料查找,得到html和xml报告集成jenkins的配置如下: 一 集成html报告 这种方式集成在你已经用gcov+lcov生成了html报告 ...

  10. nsDATA 转结构体

    很多时候需要将c,c++形式的struct转换为  NSData来处理.但是怎么转换呢? 假设有这么一个结构体: struct   MYINFO { int   a; long  b; char  c ...