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
IAP内购的更多相关文章
- JAVA项目之苹果IAP内购JAVA服务器验证流程详解
1.前言 本博客是经历过多个项目检验的, 绝对真实, 适应于对苹果iap内购稍微有些了解的JAVA开发人员, 认真看, 定能完美解决苹果内购问题. 苹果IAP内购支付实际上是"将客户端支 ...
- ios IAP 内购验证
参考我之前的笔记 苹果内购笔记,在客户端向苹果购买成功之后,我们需要进行二次验证. 二次验证 IOS在沙箱环境下购买成功之后,向苹果进行二次验证,确认用户是否购买成功. 当应用向Apple服务器请求购 ...
- IAP内购 返回的产品数量为0
上个月搞IAP,提交到appstore审核被拒,根据附件截图 可以知道是请求产品信息的时候,产品数量返回0了. 返回产品数量为0 要么是Itunes Connect 里面的Contracts Tax ...
- iOS开发苹果内购的介绍与实现
1.iOS开发苹果内购的介绍 1.1 介绍 苹果规定,凡是虚拟的物品(例如:QQ音乐的乐币)进行交易时,都必须走苹果的内购通道,苹果要收取大约30%的抽成,所以不允许接入第三方的支付方式(微信.支付宝 ...
- IAP 程序内购
最近用到IAP内置购买,阅读官方文档,在网上找了些资料,在这里作下整理,以便日后查找和修改,主要流程方向确定,文档和相关转载内容截图不一一指出,google一堆. 1.查找官方文档,两张目录截图,对主 ...
- iOS开发支付篇-内购(IAP)
一,前言 经典文章参考: . http://yimouleng.com/2015/12/17/ios-AppStore/ 内购流程 . http://www.jianshu.com/p/b199a46 ...
- Unity苹果(iOS)内购接入(Unity内置IAP)
https://www.jianshu.com/p/4045ebf81a1c Unity苹果(iOS)内购接入(Unity内置IAP) Kakarottog ...
- [Xcode 实际操作]九、实用进阶-(29)为App添加IAP(支付方式)内购项目
目录:[Swift]Xcode实际操作 首先请阅读:[Xcode10 实际操作]九.实用进阶-(28)在iTunes Connect(苹果商店的管理后台)中创建一个新的新的APP 本文将演示如何给刚刚 ...
- [Xcode 实际操作]九、实用进阶-(30)为IAP(支付方式)内购项目添加测试账号,测试内购功能
目录:[Swift]Xcode实际操作 本文将演示如何添加测试账号,以方便对内购功能进行测试. IAP,即in-App Purchase ,是一种智能移动终端应用程序付费的模式, 在苹果(Apple) ...
随机推荐
- mysql连接超时
这几天在跟踪一个项目的时候,老是发现mysql连接报timeout的异常. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException ...
- python 包管理
如果导入的模块和主程序在同个目录下,直接import就行了 2.如果导入的模块是在主程序所在目录的子目录下,可以在子目录中增加一个空白的__init__.py文件,该文件使得python解释器将子目录 ...
- mysql里group by按照分组里的内容的排序
得到一张表里按u_id分组,按count(id)排序,每个分组的pub_time最大的哪些记录,只取count(id)最大的4条 select a.u_id,a.name,a.u_name,a.id, ...
- 【英语】Bingo口语笔记(9) - 表示“不相信”
- .CO域名快被这帮搞IT的玩坏了……
鉴于近来国内访问Google的服务受阻,greatfire.org于前天推出了其基于亚马逊AWS的Google搜索镜像网站,地址是sinaapp.co.该网站随后因多家海外媒体的报道和众多微博大V的转 ...
- JVM——垃圾收集算法
1.标记-清除算法 最基础的收集算法,如其名,算法为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象. 两个不足: 1)效率问题,标记和清除两个过程的效率 ...
- linux 下查看系统内存使用情况的方法
在Windows系统中查看内存的使用情况很简单,想必大家都已经耳熟能详了,那么在linux系统如何查看内存使用情况呢?下面和大家分享在Linux 下查看内存使用情况的free命令: [root@scs ...
- Oracle查看和修改其最大的游标数
原文 Oracle查看和修改其最大的游标数 以下的文章主要是介绍Oracle查看和修改其最大的游标数,本文主要是通过相关代码的方式来引出Oracle查看和修改其最大的游标数的实际操作步骤,以下就是文章 ...
- Android无限级树状结构
通过对ListView简单的扩展.再封装,即可实现无限层级的树控件TreeView. package cn.asiontang.nleveltreelistview; import android.a ...
- Android百度地图开发(四)线路搜索
一.标注驾车线路搜索 1.首先需要定义一个起点和一个终点 // 定义一个起始点和终点 private MKPlanNode start; private MKPlanNode end; 2.实例化地图 ...