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. mysql连接超时

    这几天在跟踪一个项目的时候,老是发现mysql连接报timeout的异常. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException ...

  2. python 包管理

    如果导入的模块和主程序在同个目录下,直接import就行了 2.如果导入的模块是在主程序所在目录的子目录下,可以在子目录中增加一个空白的__init__.py文件,该文件使得python解释器将子目录 ...

  3. mysql里group by按照分组里的内容的排序

    得到一张表里按u_id分组,按count(id)排序,每个分组的pub_time最大的哪些记录,只取count(id)最大的4条 select a.u_id,a.name,a.u_name,a.id, ...

  4. 【英语】Bingo口语笔记(9) - 表示“不相信”

  5. .CO域名快被这帮搞IT的玩坏了……

    鉴于近来国内访问Google的服务受阻,greatfire.org于前天推出了其基于亚马逊AWS的Google搜索镜像网站,地址是sinaapp.co.该网站随后因多家海外媒体的报道和众多微博大V的转 ...

  6. JVM——垃圾收集算法

    1.标记-清除算法 最基础的收集算法,如其名,算法为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象. 两个不足: 1)效率问题,标记和清除两个过程的效率 ...

  7. linux 下查看系统内存使用情况的方法

    在Windows系统中查看内存的使用情况很简单,想必大家都已经耳熟能详了,那么在linux系统如何查看内存使用情况呢?下面和大家分享在Linux 下查看内存使用情况的free命令: [root@scs ...

  8. Oracle查看和修改其最大的游标数

    原文 Oracle查看和修改其最大的游标数 以下的文章主要是介绍Oracle查看和修改其最大的游标数,本文主要是通过相关代码的方式来引出Oracle查看和修改其最大的游标数的实际操作步骤,以下就是文章 ...

  9. Android无限级树状结构

    通过对ListView简单的扩展.再封装,即可实现无限层级的树控件TreeView. package cn.asiontang.nleveltreelistview; import android.a ...

  10. Android百度地图开发(四)线路搜索

    一.标注驾车线路搜索 1.首先需要定义一个起点和一个终点 // 定义一个起始点和终点 private MKPlanNode start; private MKPlanNode end; 2.实例化地图 ...