#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SandBoxHelper : NSObject
// 程序主目录,可见子目录(3个):Documents、Library、tmp
+ (NSString *)homePath;
// 程序目录,不能存任何东西
+ (NSString *)appPath;
// 文档目录,需要ITUNES同步备份的数据存这里,可存放用户数据
+ (NSString *)docPath;
// 配置目录,配置文件存这里
+ (NSString *)libPrefPath;
// 缓存目录,系统永远不会删除这里的文件,ITUNES会删除
+ (NSString *)libCachePath;
// 临时缓存目录,APP退出后,系统可能会删除这里的内容
+ (NSString *)tmpPath;
//用于存储iap内购返回的购买凭证
+ (NSString *)iapReceiptPath;
//存储成功订单的方法
+(NSString *)SuccessIapPath;
//存储崩溃日志的方法;
+(NSString *)crashLogInfo;
//存储退出资源的路径
+(NSString *)exitResourePath;
//保存临时订单
+(NSString *)tempOrderPath;
@end
NS_ASSUME_NONNULL_END
#import "SandBoxHelper.h"
@implementation SandBoxHelper
+ (NSString *)homePath {
return NSHomeDirectory();
}
+ (NSString *)appPath {
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSUserDomainMask, YES);
return [paths objectAtIndex:0];
}
+ (NSString *)docPath {
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
return [paths objectAtIndex:0];
}
+ (NSString *)libPrefPath {
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
return [[paths objectAtIndex:0] stringByAppendingFormat:@"/Preferences"];
}
+ (NSString *)libCachePath {
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
return [[paths objectAtIndex:0] stringByAppendingFormat:@"/Caches"];
}
+ (NSString *)tmpPath {
return [NSHomeDirectory() stringByAppendingFormat:@"/tmp"];
}
+ (NSString *)iapReceiptPath {
NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/EACEF35FE363A75A"];
[self hasLive:path];
return path;
}
+ (BOOL)hasLive:(NSString *)path
{
if ( NO == [[NSFileManager defaultManager] fileExistsAtPath:path] )
{
return [[NSFileManager defaultManager] createDirectoryAtPath:path
withIntermediateDirectories:YES
attributes:nil
error:NULL];
}
return YES;
}
+(NSString *)SuccessIapPath{
NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/SuccessReceiptPath"];
[self hasLive:path];
return path;
}
+(NSString *)exitResourePath{
NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/ExitResourePath"];
[self hasLive:path];
return path;
}
+(NSString *)tempOrderPath{
NSString *path = [[self libPrefPath] stringByAppendingFormat:@"/tempOrderPath"];
[self hasLive:path];
return path;
}
+(NSString *)crashLogInfo{
NSString * path = [[self libPrefPath]stringByAppendingFormat:@"/crashLogInfoPath"];
[self hasLive:path];
return path;
}
@end
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
block
@param isSuccess 是否支付成功
@param certificate 支付成功得到的凭证(用于在自己服务器验证)
@param errorMsg 错误信息
*/
typedef void(^PayResult)(BOOL isSuccess, NSString *__nullable certificate,NSString * __nullable errorMsg);
@interface IPAPurchase : NSObject
/**
*
*/
@property (nonatomic, copy) PayResult payResultBlock;
+(instancetype)manager;
/**
开启内购监听 在程序入口didFinishLaunchingWithOptions实现
*/
-(void)startManager;
/**
停止内购监听 在AppDelegate.m中的applicationWillTerminate方法实现
*/
-(void)stopManager;
/**
拉起内购支付
@param productID 内购商品ID
@param payResult 结果
*/
-(void)buyProductWithProductID:(NSString *)productID payResult:(PayResult)payResult;
/**
*订单编号
*/
@property (nonatomic, copy) NSString * orderNumber;
/**
*用户id
*/
@property (nonatomic, copy) NSString * userid;
/**
*
*/
@property (nonatomic, assign) CGFloat tbCount;
@end
NS_ASSUME_NONNULL_END
#import "IPAPurchase.h"
#import <StoreKit/StoreKit.h>
#import "SandBoxHelper.h"
#import "NSString+Chinese.h"
static NSString *const receiptKey = @"receiptKey";
dispatch_queue_t iap_queue(){
static dispatch_queue_t as_iap_queue;
static dispatch_once_t onceToken_iap_queue;
dispatch_once(&onceToken_iap_queue, ^{
as_iap_queue = dispatch_queue_create("com.iap.queue", DISPATCH_QUEUE_CONCURRENT);
});
return as_iap_queue;
}
@interface IPAPurchase()
<SKPaymentQueueDelegate, SKProductsRequestDelegate>
/**
*
*/
@property (nonatomic, strong) SKProductsRequest * request;
/**
*购买凭证(存储base64编码的交易凭证)
*/
@property (nonatomic, copy) NSString * receipt;
/**
*
*/
@property (nonatomic, copy) NSString * productId;
@end
static IPAPurchase *manager = nil;
@implementation IPAPurchase
+(instancetype)manager{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if(!manager){
manager = [[IPAPurchase alloc] init];
}
});
return manager;
}
#pragma mark -- 漏单处理
-(void)startManager{
dispatch_sync(iap_queue(), ^{
[[SKPaymentQueue defaultQueue] addTransactionObserver:manager];
});
}
#pragma mark -- 移除交易事件
-(void)stopManager{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
});
}
#pragma mark -- 发起购买的方法
-(void)buyProductWithProductID:(NSString *)productID payResult:(PayResult)payResult{
self.payResultBlock = payResult;
[self removeAllUncompleteTransactionBeforeStartNewtransaction];
//购买中HUD。。。
[self showWhiteHUDWithText:@" 购买中... "];
self.productId = productID;
if(!self.productId.length){
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"温馨提示" message:@"没有对应的商品" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
[alertView show];
}
if([SKPaymentQueue canMakePayments]){
[self requestProductInfo:self.productId];
}
else{
UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"温馨提示" message:@"请先开启应用内付费购买功能。" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
[alertView show];
}
}
#pragma mark -- 结束上次未完成的交易 防止串单
-(void)removeAllUncompleteTransactionBeforeStartNewtransaction{
NSArray *transactionsArray = [SKPaymentQueue defaultQueue].transactions;
if(transactionsArray.count >0){
//检测是否有未完成的交易
SKPaymentTransaction *transaction = [transactionsArray firstObject];
if(transaction.transactionState == SKPaymentTransactionStatePurchased){
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
return;
}
}
}
#pragma mark --发起购买请求
-(void)requestProductInfo:(NSString *)productId{
NSArray *productArray = [[NSArray alloc] initWithObjects:productId, nil];
NSSet *IDSet = [NSSet setWithArray:productArray];
self.request = [[SKProductsRequest alloc] initWithProductIdentifiers:IDSet];
self.request.delegate = self;
[self.request start];
}
#pragma mark -- SKProductsRequestDelegate 查询成功后的回调
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{
// [self dismissCustom];
NSArray *myProduct = response.products;
if(myProduct.count == 0){
//没有该商品信息HUD。。。
[self showErrorText:@"无法获取产品信息,购买失败"];
if (self.payResultBlock) {
self.payResultBlock(NO, nil, @"无法获取产品信息,购买失败");
}
return;
}
SKProduct *product = nil;
for (SKProduct *pro in myProduct) {
NSLog(@"SKProduct 描述信息%@", [pro description]);
NSLog(@"产品标题 %@" , pro.localizedTitle);
NSLog(@"产品描述信息: %@" , pro.localizedDescription);
NSLog(@"价格: %@" , pro.price);
NSLog(@"Product id: %@" , pro.productIdentifier);
if([pro.productIdentifier isEqualToString:self.productId]){
product = pro;
break;
}
}
if(product){
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
payment.applicationUsername = self.orderNumber;
[[SKPaymentQueue defaultQueue] addPayment:payment];
}
else{
NSLog(@"没有此商品");
}
}
//查询失败后的回调
-(void)request:(SKRequest *)request didFailWithError:(NSError *)error{
[self dismissCustom];
if (self.payResultBlock) {
self.payResultBlock(NO, nil, [error localizedDescription]);
}
}
////如果没有设置监听购买结果将直接跳至反馈结束
-(void)requestDidFinish:(SKRequest *)request{
// [self dismissCustom];
}
#pragma mark -- 监听结果
-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
//当用户购买的操作有结果时,就会触发下面的回调函数,
for (SKPaymentTransaction * transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased:{
[self completeTransaction:transaction];
// [self dismissCustom];
}break;
case SKPaymentTransactionStateFailed:{
[self failedTransaction:transaction];
[self dismissCustom];
}break;
case SKPaymentTransactionStateRestored:{//已经购买过该商品
[self restoreTransaction:transaction];
[self dismissCustom];
}break;
case SKPaymentTransactionStatePurchasing:{
NSLog(@"正在购买中...");
}break;
case SKPaymentTransactionStateDeferred:{
NSLog(@"最终状态未确定");
[self dismissCustom];
}break;
default:
break;
}
}
}
//完成交易
#pragma mark -- 交易完成的回调
- (void)completeTransaction:(SKPaymentTransaction *)transaction{
NSLog(@"购买成功,准备验证发货");
[self getReceipt]; //获取交易成功后的购买凭证
[self saveReceipt:transaction]; //存储交易凭证
[self checkIAPFiles:transaction];
}
#pragma mark -- 处理交易失败回调
- (void)failedTransaction:(SKPaymentTransaction *)transaction
{
NSString *error = nil;
if(transaction.error.code != SKErrorPaymentCancelled) {
//购买失败HUD。。。
[self showSuccessText:@"购买失败"];
} else {
//取消购买HUD。。。
[self showSuccessText:@"取消购买"];
}
if (self.payResultBlock) {
self.payResultBlock(NO, nil, error);
}
[[SKPaymentQueue defaultQueue]finishTransaction:transaction];
}
- (void)restoreTransaction:(SKPaymentTransaction *)transaction{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
#pragma mark -- 获取购买凭证
-(void)getReceipt{
NSURL * receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSData * receiptData = [NSData dataWithContentsOfURL:receiptUrl];
NSString * base64String = [receiptData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
self.receipt = base64String;
}
#pragma mark -- 存储购买凭证
-(void)saveReceipt:(SKPaymentTransaction *)transaction{
NSString * userId;
NSString * order;
if (self.userid) {
userId = self.userid;
[[NSUserDefaults standardUserDefaults]setObject:userId forKey:@"unlock_iap_userId"];
}else{
userId = [[NSUserDefaults standardUserDefaults]objectForKey:@"unlock_iap_userId"];
}
order = transaction.payment.applicationUsername;
NSString *fileName = [NSString UUID];
NSString *savedPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper iapReceiptPath], fileName];
NSMutableDictionary * dic = [[NSMutableDictionary alloc]init];
[dic setValue: self.receipt forKey:receiptKey];
[dic setValue: userId forKey:@"user_id"];
[dic setValue: order forKey:@"order"];
BOOL ifWriteSuccess = [dic writeToFile:savedPath atomically:YES];
if (ifWriteSuccess) {
NSLog(@"购买凭据存储成功!");
}
}
#pragma mark -- 验证本地数据
-(void)checkIAPFiles:(SKPaymentTransaction *)transaction{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
NSArray *cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error];
if (error == nil) {
for (NSString *name in cacheFileNameArray) {
if ([name hasSuffix:@".plist"]){ //如果有plist后缀的文件,说明就是存储的购买凭证
NSString *filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];
[self sendAppStoreRequestBuyPlist:filePath trans:transaction];
}
}
} else {
}
}
#pragma mark -- 存储成功订单
-(void)SaveIapSuccessReceiptDataWithReceipt:(NSString *)receipt Order:(NSString *)order UserId:(NSString *)userId{
NSMutableDictionary * mdic = [[NSMutableDictionary alloc]init];
[mdic setValue:[self getCurrentZoneTime] forKey:@"time"];
[mdic setValue: order forKey:@"order"];
[mdic setValue: userId forKey:@"userid"];
[mdic setValue: receipt forKey:receiptKey];
NSString *fileName = [NSString UUID];
NSString * successReceiptPath = [NSString stringWithFormat:@"%@/%@.plist", [SandBoxHelper SuccessIapPath], fileName];
//存储购买成功的凭证
[mdic writeToFile:successReceiptPath atomically:YES];
}
#pragma mark -- 获取系统时间的方法
-(NSString *)getCurrentZoneTime{
NSDate * date = [NSDate date];
NSDateFormatter*formatter = [[NSDateFormatter alloc]init];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSString*dateTime = [formatter stringFromDate:date];
return dateTime;
}
#pragma mark -- 去服务器验证购买
-(void)sendAppStoreRequestBuyPlist:(NSString *)plistPath trans:(SKPaymentTransaction *)transaction{
// [self showWhiteHUDWithText:@"验证中..."];
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
NSString * receipt = [dic objectForKey:receiptKey];
NSString * order = [dic objectForKey:@"order"];
NSString * userId = [dic objectForKey:@"user_id"];
#pragma mark -- 发送信息去验证是否成功
WeakSelf
[JHNetworkHelper requestPOST:@"iapPay/yz_iappay" parameters:@{@"orderNo":order,@"receipt":receipt} modelClass:nil success:^(id responseObject) {
[weakSelf showSuccessText:@"购买成功"];
weakSelf.tbCount = [responseObject[@"result"][@"money"] floatValue];
[[NSUserDefaults standardUserDefaults]removeObjectForKey:@"unlock_iap_userId"];
NSData * data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];
NSString *result = [data base64EncodedStringWithOptions:0];
if (weakSelf.payResultBlock) {
weakSelf.payResultBlock(YES, result, nil);
}
//这里将成功但存储起来
[weakSelf SaveIapSuccessReceiptDataWithReceipt:receipt Order:order UserId:userId];
[weakSelf successConsumptionOfGoodsWithOrder:order];
} failure:^(NSError *error) {
[weakSelf dismissCustom];
}];
// [[ULSDKAPI shareAPI] sendVertifyWithReceipt:receipt order:order success:^(ULSDKAPI *api, id responseObject) {
//
// if (RequestSuccess) {
//
// NSLog(@"服务器验证成功!");
//
// [[SKPaymentQueue defaultQueue]finishTransaction:transaction];
//
// [RRHUD hide];
//
// [RRHUD showSuccessWithContainerView:UL_rootVC.view status:NSLocalizedString(@"购买成功", @"")];
//
// [[NSUserDefaults standardUserDefaults]removeObjectForKey:@"unlock_iap_userId"];
// NSData * data = [NSData dataWithContentsOfFile:[[[NSBundle mainBundle] appStoreReceiptURL] path]];
//
// NSString *result = [data base64EncodedStringWithOptions:0];
//
// if (self.payResultBlock) {
// self.payResultBlock(YES, result, nil);
// }
// //这里将成功但存储起来
// [self SaveIapSuccessReceiptDataWithReceipt:receipt Order:order UserId:userId];
//
// [self successConsumptionOfGoodsWithOrder:order];
//
// }else{
// //在这里向服务器发送验证失败相关信息
// } failure:^(ULSDKAPI *api, NSString *failure) {
//
// }
}
#pragma mark -- 根据订单号来移除本地凭证的方法
-(void)successConsumptionOfGoodsWithOrder:(NSString * )cpOrder{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError * error;
if ([fileManager fileExistsAtPath:[SandBoxHelper iapReceiptPath]]) {
NSArray * cacheFileNameArray = [fileManager contentsOfDirectoryAtPath:[SandBoxHelper iapReceiptPath] error:&error];
if (error == nil) {
for (NSString * name in cacheFileNameArray) {
NSString * filePath = [NSString stringWithFormat:@"%@/%@", [SandBoxHelper iapReceiptPath], name];
[self removeReceiptWithPlistPath:filePath ByCpOrder:cpOrder];
}
}
}
}
#pragma mark -- 根据订单号来删除 存储的凭证
-(void)removeReceiptWithPlistPath:(NSString *)plistPath ByCpOrder:(NSString *)cpOrder{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError * error;
NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
NSString * order = [dic objectForKey:@"order"];
if ([cpOrder isEqualToString:order]) {
//移除与用户id订单号一样的plist 文件
BOOL ifRemove = [fileManager removeItemAtPath:plistPath error:&error];
if (ifRemove) {
NSLog(@"成功订单移除成功");
}else{
NSLog(@"成功订单移除失败");
}
}else{
NSLog(@"本地无与之匹配的订单");
}
}
@end