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. HDU 5358 First One 求和(序列求和,优化)

    题意:给定一个含n个元素的序列,求下式子的结果.S(i,j)表示为seq[i...j]之和.注:对于log20可视为1.数据量n<=105. 思路:即使能够在O(1)的时间内求得任意S,也是需要 ...

  2. android 自定义组件-带图片的textView

    1. 定义属性 <?xml version="1.0" encoding="utf-8"?> <resources> <decla ...

  3. 用于分类的决策树(Decision Tree)-ID3 C4.5

    决策树(Decision Tree)是一种基本的分类与回归方法(ID3.C4.5和基于 Gini 的 CART 可用于分类,CART还可用于回归).决策树在分类过程中,表示的是基于特征对实例进行划分, ...

  4. php里少用到的session_module_name,以及session的key值限制,简单将session存储为json格式数据的方法

    这个函数的作用就是动态的设置php.ini里的session_save_handler,配合session_set_savepath可以在程序里自由配置session的后台方式. session_ca ...

  5. Heritrix源码分析(十) Heritrix中的Http Status Code(Http状态码)(转)

    本博客属原创文章,欢迎转载!转载请务必注明出处:http://guoyunsky.iteye.com/blog/649737       本博客已迁移到本人独立博客: http://www.yun5u ...

  6. Linux下ps命令

    简述 Linux中的ps命令是Process Status的缩写.ps命令用来列出系统中当前运行的那些进程.ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,动态的显示进程信息 ...

  7. 扩容盘、SD卡扩容

    内存卡的前世今生回想当年,大家都还在用着非智能机,由于功能单一,需要存储的数据也就是通讯录和短信.虽然那时也有手机游戏,但大多都是几十KB,并不需要太大的存储空间.但随着手机功能的多样化,尤其是音乐. ...

  8. jquery validate如何不提交表单就做验证(ajax提交数据)

    if($("#FromID").valid()){ $.ajax({ type:'post', url:'/CampaignOrderRelations/save', data:{ ...

  9. jQuery和CSS 3定制HTML 5视频播放器

    目前,随着越来越多的浏览器开始支持更多的HTML5新特性,开发者也逐渐关注HTML5的开发.在众多HTML5的新特性中,视频方面的新特性是 很值得开发者和用户关注的.现在,只需要有支持HTML5的浏览 ...

  10. android性能小贴士 翻译

    转自http://developer.android.com/training/articles/perf-tips.html 性能小贴士: 这篇文档主要一些微优化可以提升应用程序性能,但是这些改变不 ...